feat(v1.8.0): IMPL_020 Note & Checklist Sorting
- Add SortOption enum for note sorting (Updated, Created, Title, Type) - Add SortDirection enum with ASCENDING/DESCENDING and toggle() - Add ChecklistSortOption enum for in-editor sorting (Manual, Alphabetical, Unchecked/Checked First) - Implement persistent note sort preferences in SharedPreferences - Add SortDialog for main screen with sort option and direction selection - Add ChecklistSortDialog for editor screen with current sort option state - Implement sort logic in MainViewModel with combined sortedNotes StateFlow - Implement sort logic in NoteEditorViewModel with auto-sort for MANUAL and UNCHECKED_FIRST - Add separator display logic for MANUAL and UNCHECKED_FIRST sort options - Add 16 sorting-related strings (English and German) - Update Constants.kt with sort preference keys - Update MainScreen.kt, NoteEditorScreen.kt with sort UI integration
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
package dev.dettmer.simplenotes.models
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🆕 v1.8.0: Sortieroptionen für Checklist-Items im Editor
|
||||||
|
*/
|
||||||
|
enum class ChecklistSortOption {
|
||||||
|
/** Manuelle Reihenfolge (Drag & Drop) — kein Re-Sort */
|
||||||
|
MANUAL,
|
||||||
|
|
||||||
|
/** Alphabetisch A→Z */
|
||||||
|
ALPHABETICAL_ASC,
|
||||||
|
|
||||||
|
/** Alphabetisch Z→A */
|
||||||
|
ALPHABETICAL_DESC,
|
||||||
|
|
||||||
|
/** Unchecked zuerst, dann Checked */
|
||||||
|
UNCHECKED_FIRST,
|
||||||
|
|
||||||
|
/** Checked zuerst, dann Unchecked */
|
||||||
|
CHECKED_FIRST
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package dev.dettmer.simplenotes.models
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🆕 v1.8.0: Sortierrichtung
|
||||||
|
*/
|
||||||
|
enum class SortDirection(val prefsValue: String) {
|
||||||
|
ASCENDING("asc"),
|
||||||
|
DESCENDING("desc");
|
||||||
|
|
||||||
|
fun toggle(): SortDirection = when (this) {
|
||||||
|
ASCENDING -> DESCENDING
|
||||||
|
DESCENDING -> ASCENDING
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromPrefsValue(value: String): SortDirection {
|
||||||
|
return entries.find { it.prefsValue == value } ?: DESCENDING
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package dev.dettmer.simplenotes.models
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🆕 v1.8.0: Sortieroptionen für die Notizliste
|
||||||
|
*/
|
||||||
|
enum class SortOption(val prefsValue: String) {
|
||||||
|
/** Zuletzt bearbeitete zuerst (Default) */
|
||||||
|
UPDATED_AT("updatedAt"),
|
||||||
|
|
||||||
|
/** Zuletzt erstellte zuerst */
|
||||||
|
CREATED_AT("createdAt"),
|
||||||
|
|
||||||
|
/** Alphabetisch nach Titel */
|
||||||
|
TITLE("title"),
|
||||||
|
|
||||||
|
/** Nach Notiz-Typ (Text / Checkliste) */
|
||||||
|
NOTE_TYPE("noteType");
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromPrefsValue(value: String): SortOption {
|
||||||
|
return entries.find { it.prefsValue == value } ?: UPDATED_AT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,11 +35,16 @@ class NotesStorage(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt alle Notizen aus dem lokalen Speicher.
|
||||||
|
*
|
||||||
|
* 🔀 v1.8.0: Sortierung entfernt — wird jetzt im ViewModel durchgeführt,
|
||||||
|
* damit der User die Sortierung konfigurieren kann.
|
||||||
|
*/
|
||||||
fun loadAllNotes(): List<Note> {
|
fun loadAllNotes(): List<Note> {
|
||||||
return notesDir.listFiles()
|
return notesDir.listFiles()
|
||||||
?.filter { it.extension == "json" }
|
?.filter { it.extension == "json" }
|
||||||
?.mapNotNull { Note.fromJson(it.readText()) }
|
?.mapNotNull { Note.fromJson(it.readText()) }
|
||||||
?.sortedByDescending { it.updatedAt }
|
|
||||||
?: emptyList()
|
?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@@ -23,6 +24,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
|||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.automirrored.outlined.Sort
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.material.icons.filled.Save
|
import androidx.compose.material.icons.filled.Save
|
||||||
@@ -57,9 +59,11 @@ import androidx.compose.ui.unit.IntOffset
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
import dev.dettmer.simplenotes.R
|
import dev.dettmer.simplenotes.R
|
||||||
|
import dev.dettmer.simplenotes.models.ChecklistSortOption
|
||||||
import dev.dettmer.simplenotes.models.NoteType
|
import dev.dettmer.simplenotes.models.NoteType
|
||||||
import dev.dettmer.simplenotes.ui.editor.components.CheckedItemsSeparator
|
import dev.dettmer.simplenotes.ui.editor.components.CheckedItemsSeparator
|
||||||
import dev.dettmer.simplenotes.ui.editor.components.ChecklistItemRow
|
import dev.dettmer.simplenotes.ui.editor.components.ChecklistItemRow
|
||||||
|
import dev.dettmer.simplenotes.ui.editor.components.ChecklistSortDialog
|
||||||
import dev.dettmer.simplenotes.ui.main.components.DeleteConfirmationDialog
|
import dev.dettmer.simplenotes.ui.main.components.DeleteConfirmationDialog
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import dev.dettmer.simplenotes.utils.showToast
|
import dev.dettmer.simplenotes.utils.showToast
|
||||||
@@ -87,6 +91,8 @@ fun NoteEditorScreen(
|
|||||||
val isOfflineMode by viewModel.isOfflineMode.collectAsState()
|
val isOfflineMode by viewModel.isOfflineMode.collectAsState()
|
||||||
|
|
||||||
var showDeleteDialog by remember { mutableStateOf(false) }
|
var showDeleteDialog by remember { mutableStateOf(false) }
|
||||||
|
var showChecklistSortDialog by remember { mutableStateOf(false) } // 🔀 v1.8.0
|
||||||
|
val lastChecklistSortOption by viewModel.lastChecklistSortOption.collectAsState() // 🔀 v1.8.0
|
||||||
var focusNewItemId by remember { mutableStateOf<String?>(null) }
|
var focusNewItemId by remember { mutableStateOf<String?>(null) }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
@@ -222,6 +228,7 @@ fun NoteEditorScreen(
|
|||||||
items = checklistItems,
|
items = checklistItems,
|
||||||
scope = scope,
|
scope = scope,
|
||||||
focusNewItemId = focusNewItemId,
|
focusNewItemId = focusNewItemId,
|
||||||
|
currentSortOption = lastChecklistSortOption, // 🔀 v1.8.0
|
||||||
onTextChange = { id, text -> viewModel.updateChecklistItemText(id, text) },
|
onTextChange = { id, text -> viewModel.updateChecklistItemText(id, text) },
|
||||||
onCheckedChange = { id, checked -> viewModel.updateChecklistItemChecked(id, checked) },
|
onCheckedChange = { id, checked -> viewModel.updateChecklistItemChecked(id, checked) },
|
||||||
onDelete = { id -> viewModel.deleteChecklistItem(id) },
|
onDelete = { id -> viewModel.deleteChecklistItem(id) },
|
||||||
@@ -235,6 +242,7 @@ fun NoteEditorScreen(
|
|||||||
},
|
},
|
||||||
onMove = { from, to -> viewModel.moveChecklistItem(from, to) },
|
onMove = { from, to -> viewModel.moveChecklistItem(from, to) },
|
||||||
onFocusHandled = { focusNewItemId = null },
|
onFocusHandled = { focusNewItemId = null },
|
||||||
|
onSortClick = { showChecklistSortDialog = true }, // 🔀 v1.8.0
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
@@ -260,6 +268,18 @@ fun NoteEditorScreen(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔀 v1.8.0: Checklist Sort Dialog
|
||||||
|
if (showChecklistSortDialog) {
|
||||||
|
ChecklistSortDialog(
|
||||||
|
currentOption = lastChecklistSortOption,
|
||||||
|
onOptionSelected = { option ->
|
||||||
|
viewModel.sortChecklistItems(option)
|
||||||
|
showChecklistSortDialog = false
|
||||||
|
},
|
||||||
|
onDismiss = { showChecklistSortDialog = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -309,6 +329,7 @@ private fun ChecklistEditor(
|
|||||||
items: List<ChecklistItemState>,
|
items: List<ChecklistItemState>,
|
||||||
scope: kotlinx.coroutines.CoroutineScope,
|
scope: kotlinx.coroutines.CoroutineScope,
|
||||||
focusNewItemId: String?,
|
focusNewItemId: String?,
|
||||||
|
currentSortOption: ChecklistSortOption, // 🔀 v1.8.0: Aktuelle Sortierung
|
||||||
onTextChange: (String, String) -> Unit,
|
onTextChange: (String, String) -> Unit,
|
||||||
onCheckedChange: (String, Boolean) -> Unit,
|
onCheckedChange: (String, Boolean) -> Unit,
|
||||||
onDelete: (String) -> Unit,
|
onDelete: (String) -> Unit,
|
||||||
@@ -316,6 +337,7 @@ private fun ChecklistEditor(
|
|||||||
onAddItemAtEnd: () -> Unit,
|
onAddItemAtEnd: () -> Unit,
|
||||||
onMove: (Int, Int) -> Unit,
|
onMove: (Int, Int) -> Unit,
|
||||||
onFocusHandled: () -> Unit,
|
onFocusHandled: () -> Unit,
|
||||||
|
onSortClick: () -> Unit, // 🔀 v1.8.0
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
@@ -325,10 +347,12 @@ private fun ChecklistEditor(
|
|||||||
onMove = onMove
|
onMove = onMove
|
||||||
)
|
)
|
||||||
|
|
||||||
// 🆕 v1.8.0 (IMPL_017): Separator-Position berechnen
|
// 🆕 v1.8.0 (IMPL_017 + IMPL_020): Separator nur bei MANUAL und UNCHECKED_FIRST anzeigen
|
||||||
val uncheckedCount = items.count { !it.isChecked }
|
val uncheckedCount = items.count { !it.isChecked }
|
||||||
val checkedCount = items.count { it.isChecked }
|
val checkedCount = items.count { it.isChecked }
|
||||||
val showSeparator = uncheckedCount > 0 && checkedCount > 0
|
val shouldShowSeparator = currentSortOption == ChecklistSortOption.MANUAL ||
|
||||||
|
currentSortOption == ChecklistSortOption.UNCHECKED_FIRST
|
||||||
|
val showSeparator = shouldShowSeparator && uncheckedCount > 0 && checkedCount > 0
|
||||||
|
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
@@ -396,11 +420,15 @@ private fun ChecklistEditor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Item Button
|
// 🔀 v1.8.0: Add Item Button + Sort Button
|
||||||
TextButton(
|
Row(
|
||||||
onClick = onAddItemAtEnd,
|
modifier = Modifier
|
||||||
modifier = Modifier.padding(start = 8.dp)
|
.fillMaxWidth()
|
||||||
|
.padding(start = 8.dp, end = 8.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
TextButton(onClick = onAddItemAtEnd) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Add,
|
imageVector = Icons.Default.Add,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
@@ -408,6 +436,15 @@ private fun ChecklistEditor(
|
|||||||
)
|
)
|
||||||
Text(stringResource(R.string.add_item))
|
Text(stringResource(R.string.add_item))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IconButton(onClick = onSortClick) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Outlined.Sort,
|
||||||
|
contentDescription = stringResource(R.string.sort_checklist),
|
||||||
|
modifier = androidx.compose.ui.Modifier.padding(4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import dev.dettmer.simplenotes.models.ChecklistItem
|
import dev.dettmer.simplenotes.models.ChecklistItem
|
||||||
|
import dev.dettmer.simplenotes.models.ChecklistSortOption
|
||||||
import dev.dettmer.simplenotes.models.Note
|
import dev.dettmer.simplenotes.models.Note
|
||||||
import dev.dettmer.simplenotes.models.NoteType
|
import dev.dettmer.simplenotes.models.NoteType
|
||||||
import dev.dettmer.simplenotes.models.SyncStatus
|
import dev.dettmer.simplenotes.models.SyncStatus
|
||||||
@@ -65,6 +66,10 @@ class NoteEditorViewModel(
|
|||||||
)
|
)
|
||||||
val isOfflineMode: StateFlow<Boolean> = _isOfflineMode.asStateFlow()
|
val isOfflineMode: StateFlow<Boolean> = _isOfflineMode.asStateFlow()
|
||||||
|
|
||||||
|
// 🔀 v1.8.0 (IMPL_020): Letzte Checklist-Sortierung (Session-Scope)
|
||||||
|
private val _lastChecklistSortOption = MutableStateFlow(ChecklistSortOption.MANUAL)
|
||||||
|
val lastChecklistSortOption: StateFlow<ChecklistSortOption> = _lastChecklistSortOption.asStateFlow()
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
// Events
|
// Events
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
@@ -182,8 +187,14 @@ class NoteEditorViewModel(
|
|||||||
val updatedItems = items.map { item ->
|
val updatedItems = items.map { item ->
|
||||||
if (item.id == itemId) item.copy(isChecked = isChecked) else item
|
if (item.id == itemId) item.copy(isChecked = isChecked) else item
|
||||||
}
|
}
|
||||||
// 🆕 v1.8.0 (IMPL_017): Nach Toggle sortieren
|
// 🆕 v1.8.0 (IMPL_017 + IMPL_020): Auto-Sort nur bei MANUAL und UNCHECKED_FIRST
|
||||||
|
val currentSort = _lastChecklistSortOption.value
|
||||||
|
if (currentSort == ChecklistSortOption.MANUAL || currentSort == ChecklistSortOption.UNCHECKED_FIRST) {
|
||||||
sortChecklistItems(updatedItems)
|
sortChecklistItems(updatedItems)
|
||||||
|
} else {
|
||||||
|
// Bei anderen Sortierungen (alphabetisch, checked first) nicht auto-sortieren
|
||||||
|
updatedItems.mapIndexed { index, item -> item.copy(order = index) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,6 +252,37 @@ class NoteEditorViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🔀 v1.8.0 (IMPL_020): Sortiert Checklist-Items nach gewählter Option.
|
||||||
|
* Einmalige Aktion (nicht persistiert) — User kann danach per Drag & Drop feinjustieren.
|
||||||
|
*/
|
||||||
|
fun sortChecklistItems(option: ChecklistSortOption) {
|
||||||
|
// Merke die Auswahl für diesen Editor-Session
|
||||||
|
_lastChecklistSortOption.value = option
|
||||||
|
|
||||||
|
_checklistItems.update { items ->
|
||||||
|
val sorted = when (option) {
|
||||||
|
// Bei MANUAL: Sortiere nach checked/unchecked, damit Separator korrekt platziert wird
|
||||||
|
ChecklistSortOption.MANUAL -> items.sortedBy { it.isChecked }
|
||||||
|
|
||||||
|
ChecklistSortOption.ALPHABETICAL_ASC ->
|
||||||
|
items.sortedBy { it.text.lowercase() }
|
||||||
|
|
||||||
|
ChecklistSortOption.ALPHABETICAL_DESC ->
|
||||||
|
items.sortedByDescending { it.text.lowercase() }
|
||||||
|
|
||||||
|
ChecklistSortOption.UNCHECKED_FIRST ->
|
||||||
|
items.sortedBy { it.isChecked }
|
||||||
|
|
||||||
|
ChecklistSortOption.CHECKED_FIRST ->
|
||||||
|
items.sortedByDescending { it.isChecked }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order-Werte neu zuweisen
|
||||||
|
sorted.mapIndexed { index, item -> item.copy(order = index) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun saveNote() {
|
fun saveNote() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val state = _uiState.value
|
val state = _uiState.value
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package dev.dettmer.simplenotes.ui.editor.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
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.unit.dp
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
|
import dev.dettmer.simplenotes.models.ChecklistSortOption
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🔀 v1.8.0: Dialog zur Auswahl der Checklist-Sortierung.
|
||||||
|
*
|
||||||
|
* Einmalige Sortier-Aktion (nicht persistiert).
|
||||||
|
* User kann danach per Drag & Drop feinjustieren.
|
||||||
|
*
|
||||||
|
* ┌─────────────────────────────────┐
|
||||||
|
* │ Sort Checklist │
|
||||||
|
* ├─────────────────────────────────┤
|
||||||
|
* │ ( ) Manual │
|
||||||
|
* │ ( ) A → Z │
|
||||||
|
* │ ( ) Z → A │
|
||||||
|
* │ (●) Unchecked first │
|
||||||
|
* │ ( ) Checked first │
|
||||||
|
* ├─────────────────────────────────┤
|
||||||
|
* │ [Cancel] [Apply] │
|
||||||
|
* └─────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun ChecklistSortDialog(
|
||||||
|
currentOption: ChecklistSortOption, // 🔀 v1.8.0: Aktuelle Auswahl merken
|
||||||
|
onOptionSelected: (ChecklistSortOption) -> Unit,
|
||||||
|
onDismiss: () -> Unit
|
||||||
|
) {
|
||||||
|
var selectedOption by remember { mutableStateOf(currentOption) }
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.sort_checklist),
|
||||||
|
style = MaterialTheme.typography.headlineSmall
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
ChecklistSortOption.entries.forEach { option ->
|
||||||
|
SortOptionRow(
|
||||||
|
label = stringResource(option.toStringRes()),
|
||||||
|
isSelected = selectedOption == option,
|
||||||
|
onClick = { selectedOption = option }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text(stringResource(R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
onOptionSelected(selectedOption)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.apply))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SortOptionRow(
|
||||||
|
label: String,
|
||||||
|
isSelected: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(vertical = 4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = isSelected,
|
||||||
|
onClick = onClick
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension: ChecklistSortOption → String-Resource-ID
|
||||||
|
*/
|
||||||
|
fun ChecklistSortOption.toStringRes(): Int = when (this) {
|
||||||
|
ChecklistSortOption.MANUAL -> R.string.sort_checklist_manual
|
||||||
|
ChecklistSortOption.ALPHABETICAL_ASC -> R.string.sort_checklist_alpha_asc
|
||||||
|
ChecklistSortOption.ALPHABETICAL_DESC -> R.string.sort_checklist_alpha_desc
|
||||||
|
ChecklistSortOption.UNCHECKED_FIRST -> R.string.sort_checklist_unchecked_first
|
||||||
|
ChecklistSortOption.CHECKED_FIRST -> R.string.sort_checklist_checked_first
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import androidx.compose.material.icons.filled.Refresh
|
|||||||
import androidx.compose.material.icons.filled.SelectAll
|
import androidx.compose.material.icons.filled.SelectAll
|
||||||
import androidx.compose.material.icons.filled.Settings
|
import androidx.compose.material.icons.filled.Settings
|
||||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||||
|
import androidx.compose.material.icons.automirrored.outlined.Sort
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
// FabPosition nicht mehr benötigt - FAB wird manuell platziert
|
// FabPosition nicht mehr benötigt - FAB wird manuell platziert
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -48,6 +49,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
import dev.dettmer.simplenotes.R
|
import dev.dettmer.simplenotes.R
|
||||||
import dev.dettmer.simplenotes.models.NoteType
|
import dev.dettmer.simplenotes.models.NoteType
|
||||||
|
import dev.dettmer.simplenotes.ui.main.components.SortDialog
|
||||||
import dev.dettmer.simplenotes.sync.SyncStateManager
|
import dev.dettmer.simplenotes.sync.SyncStateManager
|
||||||
import dev.dettmer.simplenotes.ui.main.components.DeleteConfirmationDialog
|
import dev.dettmer.simplenotes.ui.main.components.DeleteConfirmationDialog
|
||||||
import dev.dettmer.simplenotes.ui.main.components.EmptyState
|
import dev.dettmer.simplenotes.ui.main.components.EmptyState
|
||||||
@@ -77,7 +79,7 @@ fun MainScreen(
|
|||||||
onOpenSettings: () -> Unit,
|
onOpenSettings: () -> Unit,
|
||||||
onCreateNote: (NoteType) -> Unit
|
onCreateNote: (NoteType) -> Unit
|
||||||
) {
|
) {
|
||||||
val notes by viewModel.notes.collectAsState()
|
val notes by viewModel.sortedNotes.collectAsState()
|
||||||
val syncState by viewModel.syncState.collectAsState()
|
val syncState by viewModel.syncState.collectAsState()
|
||||||
val scrollToTop by viewModel.scrollToTop.collectAsState()
|
val scrollToTop by viewModel.scrollToTop.collectAsState()
|
||||||
|
|
||||||
@@ -100,6 +102,11 @@ fun MainScreen(
|
|||||||
// 🆕 v1.8.0: Sync status legend dialog
|
// 🆕 v1.8.0: Sync status legend dialog
|
||||||
var showSyncLegend by remember { mutableStateOf(false) }
|
var showSyncLegend by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// 🔀 v1.8.0: Sort dialog state
|
||||||
|
var showSortDialog by remember { mutableStateOf(false) }
|
||||||
|
val sortOption by viewModel.sortOption.collectAsState()
|
||||||
|
val sortDirection by viewModel.sortDirection.collectAsState()
|
||||||
|
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
@@ -180,6 +187,7 @@ fun MainScreen(
|
|||||||
syncEnabled = canSync,
|
syncEnabled = canSync,
|
||||||
showSyncLegend = isSyncAvailable, // 🆕 v1.8.0: Nur wenn Sync verfügbar
|
showSyncLegend = isSyncAvailable, // 🆕 v1.8.0: Nur wenn Sync verfügbar
|
||||||
onSyncLegendClick = { showSyncLegend = true }, // 🆕 v1.8.0
|
onSyncLegendClick = { showSyncLegend = true }, // 🆕 v1.8.0
|
||||||
|
onSortClick = { showSortDialog = true }, // 🔀 v1.8.0
|
||||||
onSyncClick = { viewModel.triggerManualSync("toolbar") },
|
onSyncClick = { viewModel.triggerManualSync("toolbar") },
|
||||||
onSettingsClick = onOpenSettings
|
onSettingsClick = onOpenSettings
|
||||||
)
|
)
|
||||||
@@ -293,6 +301,21 @@ fun MainScreen(
|
|||||||
onDismiss = { showSyncLegend = false }
|
onDismiss = { showSyncLegend = false }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔀 v1.8.0: Sort Dialog
|
||||||
|
if (showSortDialog) {
|
||||||
|
SortDialog(
|
||||||
|
currentOption = sortOption,
|
||||||
|
currentDirection = sortDirection,
|
||||||
|
onOptionSelected = { option ->
|
||||||
|
viewModel.setSortOption(option)
|
||||||
|
},
|
||||||
|
onDirectionToggled = {
|
||||||
|
viewModel.toggleSortDirection()
|
||||||
|
},
|
||||||
|
onDismiss = { showSortDialog = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,6 +325,7 @@ private fun MainTopBar(
|
|||||||
syncEnabled: Boolean,
|
syncEnabled: Boolean,
|
||||||
showSyncLegend: Boolean, // 🆕 v1.8.0: Ob der Hilfe-Button sichtbar sein soll
|
showSyncLegend: Boolean, // 🆕 v1.8.0: Ob der Hilfe-Button sichtbar sein soll
|
||||||
onSyncLegendClick: () -> Unit, // 🆕 v1.8.0
|
onSyncLegendClick: () -> Unit, // 🆕 v1.8.0
|
||||||
|
onSortClick: () -> Unit, // 🔀 v1.8.0: Sort-Button
|
||||||
onSyncClick: () -> Unit,
|
onSyncClick: () -> Unit,
|
||||||
onSettingsClick: () -> Unit
|
onSettingsClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -313,6 +337,14 @@ private fun MainTopBar(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
|
// 🔀 v1.8.0: Sort Button
|
||||||
|
IconButton(onClick = onSortClick) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Outlined.Sort,
|
||||||
|
contentDescription = stringResource(R.string.sort_notes)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// 🆕 v1.8.0: Sync Status Legend Button (nur wenn Sync verfügbar)
|
// 🆕 v1.8.0: Sync Status Legend Button (nur wenn Sync verfügbar)
|
||||||
if (showSyncLegend) {
|
if (showSyncLegend) {
|
||||||
IconButton(onClick = onSyncLegendClick) {
|
IconButton(onClick = onSyncLegendClick) {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import android.content.Context
|
|||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dev.dettmer.simplenotes.models.Note
|
import dev.dettmer.simplenotes.models.Note
|
||||||
|
import dev.dettmer.simplenotes.models.SortDirection
|
||||||
|
import dev.dettmer.simplenotes.models.SortOption
|
||||||
import dev.dettmer.simplenotes.R
|
import dev.dettmer.simplenotes.R
|
||||||
import dev.dettmer.simplenotes.storage.NotesStorage
|
import dev.dettmer.simplenotes.storage.NotesStorage
|
||||||
import dev.dettmer.simplenotes.sync.SyncProgress
|
import dev.dettmer.simplenotes.sync.SyncProgress
|
||||||
@@ -20,6 +22,7 @@ import kotlinx.coroutines.flow.SharingStarted
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -102,6 +105,40 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
Logger.d(TAG, "🔄 refreshDisplayMode: displayMode=${_displayMode.value} → $newValue")
|
Logger.d(TAG, "🔄 refreshDisplayMode: displayMode=${_displayMode.value} → $newValue")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// 🔀 v1.8.0: Sort State
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
private val _sortOption = MutableStateFlow(
|
||||||
|
SortOption.fromPrefsValue(
|
||||||
|
prefs.getString(Constants.KEY_SORT_OPTION, Constants.DEFAULT_SORT_OPTION) ?: Constants.DEFAULT_SORT_OPTION
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val sortOption: StateFlow<SortOption> = _sortOption.asStateFlow()
|
||||||
|
|
||||||
|
private val _sortDirection = MutableStateFlow(
|
||||||
|
SortDirection.fromPrefsValue(
|
||||||
|
prefs.getString(Constants.KEY_SORT_DIRECTION, Constants.DEFAULT_SORT_DIRECTION) ?: Constants.DEFAULT_SORT_DIRECTION
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val sortDirection: StateFlow<SortDirection> = _sortDirection.asStateFlow()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🔀 v1.8.0: Sortierte Notizen — kombiniert aus Notes + SortOption + SortDirection.
|
||||||
|
* Reagiert automatisch auf Änderungen in allen drei Flows.
|
||||||
|
*/
|
||||||
|
val sortedNotes: StateFlow<List<Note>> = combine(
|
||||||
|
_notes,
|
||||||
|
_sortOption,
|
||||||
|
_sortDirection
|
||||||
|
) { notes, option, direction ->
|
||||||
|
sortNotes(notes, option, direction)
|
||||||
|
}.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.WhileSubscribed(5_000),
|
||||||
|
initialValue = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
// Sync State
|
// Sync State
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
@@ -688,6 +725,58 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// 🔀 v1.8.0: Sortierung
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🔀 v1.8.0: Sortiert Notizen nach gewählter Option und Richtung.
|
||||||
|
*/
|
||||||
|
private fun sortNotes(
|
||||||
|
notes: List<Note>,
|
||||||
|
option: SortOption,
|
||||||
|
direction: SortDirection
|
||||||
|
): List<Note> {
|
||||||
|
val comparator: Comparator<Note> = when (option) {
|
||||||
|
SortOption.UPDATED_AT -> compareBy { it.updatedAt }
|
||||||
|
SortOption.CREATED_AT -> compareBy { it.createdAt }
|
||||||
|
SortOption.TITLE -> compareBy(String.CASE_INSENSITIVE_ORDER) { it.title }
|
||||||
|
SortOption.NOTE_TYPE -> compareBy<Note> { it.noteType.ordinal }
|
||||||
|
.thenByDescending { it.updatedAt } // Sekundär: Datum innerhalb gleicher Typen
|
||||||
|
}
|
||||||
|
|
||||||
|
return when (direction) {
|
||||||
|
SortDirection.ASCENDING -> notes.sortedWith(comparator)
|
||||||
|
SortDirection.DESCENDING -> notes.sortedWith(comparator.reversed())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🔀 v1.8.0: Setzt die Sortieroption und speichert in SharedPreferences.
|
||||||
|
*/
|
||||||
|
fun setSortOption(option: SortOption) {
|
||||||
|
_sortOption.value = option
|
||||||
|
prefs.edit().putString(Constants.KEY_SORT_OPTION, option.prefsValue).apply()
|
||||||
|
Logger.d(TAG, "🔀 Sort option changed to: ${option.prefsValue}")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🔀 v1.8.0: Setzt die Sortierrichtung und speichert in SharedPreferences.
|
||||||
|
*/
|
||||||
|
fun setSortDirection(direction: SortDirection) {
|
||||||
|
_sortDirection.value = direction
|
||||||
|
prefs.edit().putString(Constants.KEY_SORT_DIRECTION, direction.prefsValue).apply()
|
||||||
|
Logger.d(TAG, "🔀 Sort direction changed to: ${direction.prefsValue}")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🔀 v1.8.0: Toggelt die Sortierrichtung.
|
||||||
|
*/
|
||||||
|
fun toggleSortDirection() {
|
||||||
|
val newDirection = _sortDirection.value.toggle()
|
||||||
|
setSortDirection(newDirection)
|
||||||
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
// Helpers
|
// Helpers
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
package dev.dettmer.simplenotes.ui.main.components
|
||||||
|
|
||||||
|
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.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.ArrowDownward
|
||||||
|
import androidx.compose.material.icons.filled.ArrowUpward
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
|
import dev.dettmer.simplenotes.models.SortDirection
|
||||||
|
import dev.dettmer.simplenotes.models.SortOption
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🔀 v1.8.0: Dialog zur Auswahl der Sortierung für die Notizliste.
|
||||||
|
*
|
||||||
|
* Zeigt RadioButtons für die Sortieroption und einen Toggle für die Richtung.
|
||||||
|
*
|
||||||
|
* ┌─────────────────────────────────┐
|
||||||
|
* │ Sort Notes │
|
||||||
|
* ├─────────────────────────────────┤
|
||||||
|
* │ (●) Last modified ↓↑ │
|
||||||
|
* │ ( ) Date created │
|
||||||
|
* │ ( ) Name │
|
||||||
|
* │ ( ) Type │
|
||||||
|
* ├─────────────────────────────────┤
|
||||||
|
* │ [Close] │
|
||||||
|
* └─────────────────────────────────┘
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun SortDialog(
|
||||||
|
currentOption: SortOption,
|
||||||
|
currentDirection: SortDirection,
|
||||||
|
onOptionSelected: (SortOption) -> Unit,
|
||||||
|
onDirectionToggled: () -> Unit,
|
||||||
|
onDismiss: () -> Unit
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.sort_notes),
|
||||||
|
style = MaterialTheme.typography.headlineSmall
|
||||||
|
)
|
||||||
|
|
||||||
|
// Direction Toggle Button
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
IconButton(onClick = onDirectionToggled) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (currentDirection == SortDirection.DESCENDING) {
|
||||||
|
Icons.Default.ArrowDownward
|
||||||
|
} else {
|
||||||
|
Icons.Default.ArrowUpward
|
||||||
|
},
|
||||||
|
contentDescription = stringResource(
|
||||||
|
if (currentDirection == SortDirection.DESCENDING) {
|
||||||
|
R.string.sort_descending
|
||||||
|
} else {
|
||||||
|
R.string.sort_ascending
|
||||||
|
}
|
||||||
|
),
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = stringResource(
|
||||||
|
if (currentDirection == SortDirection.DESCENDING) {
|
||||||
|
R.string.sort_descending
|
||||||
|
} else {
|
||||||
|
R.string.sort_ascending
|
||||||
|
}
|
||||||
|
),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
HorizontalDivider()
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
SortOption.entries.forEach { option ->
|
||||||
|
SortOptionRow(
|
||||||
|
label = stringResource(option.toStringRes()),
|
||||||
|
isSelected = currentOption == option,
|
||||||
|
onClick = { onOptionSelected(option) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text(stringResource(R.string.close))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SortOptionRow(
|
||||||
|
label: String,
|
||||||
|
isSelected: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(vertical = 4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
selected = isSelected,
|
||||||
|
onClick = onClick
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
text = label,
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension: SortOption → String-Resource-ID
|
||||||
|
*/
|
||||||
|
fun SortOption.toStringRes(): Int = when (this) {
|
||||||
|
SortOption.UPDATED_AT -> R.string.sort_by_updated
|
||||||
|
SortOption.CREATED_AT -> R.string.sort_by_created
|
||||||
|
SortOption.TITLE -> R.string.sort_by_name
|
||||||
|
SortOption.NOTE_TYPE -> R.string.sort_by_type
|
||||||
|
}
|
||||||
@@ -73,4 +73,10 @@ object Constants {
|
|||||||
const val DEFAULT_MAX_PARALLEL_DOWNLOADS = 5
|
const val DEFAULT_MAX_PARALLEL_DOWNLOADS = 5
|
||||||
const val MIN_PARALLEL_DOWNLOADS = 1
|
const val MIN_PARALLEL_DOWNLOADS = 1
|
||||||
const val MAX_PARALLEL_DOWNLOADS = 10
|
const val MAX_PARALLEL_DOWNLOADS = 10
|
||||||
|
|
||||||
|
// 🔀 v1.8.0: Sortierung
|
||||||
|
const val KEY_SORT_OPTION = "sort_option"
|
||||||
|
const val KEY_SORT_DIRECTION = "sort_direction"
|
||||||
|
const val DEFAULT_SORT_OPTION = "updatedAt"
|
||||||
|
const val DEFAULT_SORT_DIRECTION = "desc"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,25 @@
|
|||||||
<string name="sync_phase_completed">Sync abgeschlossen</string>
|
<string name="sync_phase_completed">Sync abgeschlossen</string>
|
||||||
<string name="sync_phase_error">Sync fehlgeschlagen</string>
|
<string name="sync_phase_error">Sync fehlgeschlagen</string>
|
||||||
|
|
||||||
|
<!-- 🔀 v1.8.0 (IMPL_020): Sortierung: Notizliste -->
|
||||||
|
<string name="sort_notes">Notizen sortieren</string>
|
||||||
|
<string name="sort_ascending">Aufsteigend</string>
|
||||||
|
<string name="sort_descending">Absteigend</string>
|
||||||
|
<string name="sort_by_updated">Zuletzt bearbeitet</string>
|
||||||
|
<string name="sort_by_created">Erstelldatum</string>
|
||||||
|
<string name="sort_by_name">Name</string>
|
||||||
|
<string name="sort_by_type">Typ</string>
|
||||||
|
<string name="close">Schließen</string>
|
||||||
|
|
||||||
|
<!-- 🔀 v1.8.0 (IMPL_020): Sortierung: Checkliste -->
|
||||||
|
<string name="sort_checklist">Checkliste sortieren</string>
|
||||||
|
<string name="sort_checklist_manual">Manuell</string>
|
||||||
|
<string name="sort_checklist_alpha_asc">A → Z</string>
|
||||||
|
<string name="sort_checklist_alpha_desc">Z → A</string>
|
||||||
|
<string name="sort_checklist_unchecked_first">Unerledigte zuerst</string>
|
||||||
|
<string name="sort_checklist_checked_first">Erledigte zuerst</string>
|
||||||
|
<string name="apply">Anwenden</string>
|
||||||
|
|
||||||
<!-- ============================= -->
|
<!-- ============================= -->
|
||||||
<!-- DELETE DIALOGS -->
|
<!-- DELETE DIALOGS -->
|
||||||
<!-- ============================= -->
|
<!-- ============================= -->
|
||||||
|
|||||||
@@ -88,6 +88,25 @@
|
|||||||
<string name="sync_phase_checking">Checking server…</string>
|
<string name="sync_phase_checking">Checking server…</string>
|
||||||
<string name="sync_phase_uploading">Uploading…</string>
|
<string name="sync_phase_uploading">Uploading…</string>
|
||||||
<string name="sync_phase_downloading">Downloading…</string>
|
<string name="sync_phase_downloading">Downloading…</string>
|
||||||
|
|
||||||
|
<!-- 🔀 v1.8.0 (IMPL_020): Sort: Note List -->
|
||||||
|
<string name="sort_notes">Sort notes</string>
|
||||||
|
<string name="sort_ascending">Ascending</string>
|
||||||
|
<string name="sort_descending">Descending</string>
|
||||||
|
<string name="sort_by_updated">Last modified</string>
|
||||||
|
<string name="sort_by_created">Date created</string>
|
||||||
|
<string name="sort_by_name">Name</string>
|
||||||
|
<string name="sort_by_type">Type</string>
|
||||||
|
<string name="close">Close</string>
|
||||||
|
|
||||||
|
<!-- 🔀 v1.8.0 (IMPL_020): Sort: Checklist -->
|
||||||
|
<string name="sort_checklist">Sort checklist</string>
|
||||||
|
<string name="sort_checklist_manual">Manual</string>
|
||||||
|
<string name="sort_checklist_alpha_asc">A → Z</string>
|
||||||
|
<string name="sort_checklist_alpha_desc">Z → A</string>
|
||||||
|
<string name="sort_checklist_unchecked_first">Unchecked first</string>
|
||||||
|
<string name="sort_checklist_checked_first">Checked first</string>
|
||||||
|
<string name="apply">Apply</string>
|
||||||
<string name="sync_phase_importing_markdown">Importing Markdown…</string>
|
<string name="sync_phase_importing_markdown">Importing Markdown…</string>
|
||||||
<string name="sync_phase_saving">Saving…</string>
|
<string name="sync_phase_saving">Saving…</string>
|
||||||
<string name="sync_phase_completed">Sync complete</string>
|
<string name="sync_phase_completed">Sync complete</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user