Skip to content

Commit e773a05

Browse files
committed
feat(ui): add color filter chip to note list filter bar
Adds a Palette icon chip to FilterChipRow that opens a dropdown showing available note colors with per-color counts. The color filter combines with the existing type filter (AND logic) and is persisted in SharedPreferences. Converts Text and List type chips from text labels to icon-only for visual consistency. Color filter dropdown styling is now consistent with the editor overflow menu (surfaceContainerHigh container, large rounded shape, 6dp shadow elevation). Closes #65
1 parent 5a631cb commit e773a05

6 files changed

Lines changed: 300 additions & 41 deletions

File tree

β€Žandroid/app/src/main/java/dev/dettmer/simplenotes/ui/main/MainScreen.ktβ€Ž

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ fun MainScreen(viewModel: MainViewModel, onOpenNote: (String?) -> Unit, onOpenSe
125125
val noteFilter by viewModel.noteFilter.collectAsState()
126126
// πŸ†• v1.9.0 (F10): Search query state
127127
val searchQuery by viewModel.searchQuery.collectAsState()
128+
// πŸ†• v2.5.0: Farbfilter-State
129+
val colorFilter by viewModel.colorFilter.collectAsState()
130+
val availableColors by viewModel.availableColors.collectAsState()
128131
val focusManager = LocalFocusManager.current
129132

130133
val snackbarHostState = remember { SnackbarHostState() }
@@ -265,6 +268,9 @@ fun MainScreen(viewModel: MainViewModel, onOpenNote: (String?) -> Unit, onOpenSe
265268
FilterChipRow(
266269
currentFilter = noteFilter,
267270
onFilterSelected = { viewModel.setNoteFilter(it) },
271+
currentColorFilter = colorFilter, // πŸ†• v2.5.0
272+
onColorFilterSelected = { viewModel.setColorFilter(it) }, // πŸ†• v2.5.0
273+
availableColors = availableColors, // πŸ†• v2.5.0
268274
searchQuery = searchQuery,
269275
onSearchQueryChanged = { viewModel.setSearchQuery(it) },
270276
onSortClick = { showSortDialog = true },

β€Žandroid/app/src/main/java/dev/dettmer/simplenotes/ui/main/MainViewModel.ktβ€Ž

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,18 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
210210
)
211211
val noteFilter: StateFlow<NoteFilter> = _noteFilter.asStateFlow()
212212

213+
// πŸ†• v2.5.0: Farbfilter β€” null = kein Filter, "#RRGGBB" = aktiver Farbfilter
214+
private val _colorFilter = MutableStateFlow(
215+
prefs.getString(Constants.KEY_COLOR_FILTER, Constants.DEFAULT_COLOR_FILTER)
216+
?.takeIf { it.isNotEmpty() }
217+
)
218+
val colorFilter: StateFlow<String?> = _colorFilter.asStateFlow()
219+
220+
// πŸ†• v2.5.0: Kombinierter Filter-State fΓΌr sortedNotes.
221+
// NΓΆtig, da combine() nativ max. 5 Flows unterstΓΌtzt; NoteFilter + Farbfilter werden
222+
// zu einem Paar zusammengefasst, damit sortedNotes weiterhin 5 Flows nutzt.
223+
private val _filterCriteria = combine(_noteFilter, _colorFilter) { f, c -> Pair(f, c) }
224+
213225
// πŸ†• v1.9.0 (F10): Search Query State
214226
private val _searchQuery = MutableStateFlow("")
215227
val searchQuery: StateFlow<String> = _searchQuery.asStateFlow()
@@ -218,15 +230,17 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
218230
* πŸ”€ v1.8.0: Sortierte Notizen β€” kombiniert aus Notes + SortOption + SortDirection.
219231
* πŸ†• v1.9.0 (F06): + Filter nach NoteType
220232
* πŸ†• v1.9.0 (F10): + Volltextsuche ΓΌber Titel und Inhalt
233+
* πŸ†• v2.5.0: + Farbfilter (via _filterCriteria)
221234
*/
222235
val sortedNotes: StateFlow<List<Note>> = combine(
223236
_notes,
224237
_sortOption,
225238
_sortDirection,
226-
_noteFilter,
239+
_filterCriteria, // πŸ†• v2.5.0: vorher _noteFilter
227240
_searchQuery
228-
) { notes, option, direction, filter, query ->
229-
val filtered = filterNotes(notes, filter)
241+
) { notes, option, direction, filterCriteria, query ->
242+
val (filter, colorFilter) = filterCriteria
243+
val filtered = filterNotes(notes, filter, colorFilter)
230244
val searched = searchNotes(filtered, query)
231245
val sorted = sortNotes(searched, option, direction)
232246

@@ -972,13 +986,23 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
972986

973987
/**
974988
* πŸ†• v1.9.0 (F06): Filtert Notizen nach NoteType.
989+
* πŸ†• v2.5.0: + optionaler Farbfilter (UND-VerknΓΌpfung).
975990
*/
976-
private fun filterNotes(notes: List<Note>, filter: NoteFilter): List<Note> {
977-
return when (filter) {
978-
NoteFilter.ALL -> notes
979-
NoteFilter.TEXT_ONLY -> notes.filter { it.noteType == NoteType.TEXT }
991+
private fun filterNotes(
992+
notes: List<Note>,
993+
filter: NoteFilter,
994+
colorFilter: String? = null // πŸ†• v2.5.0
995+
): List<Note> {
996+
val byType = when (filter) {
997+
NoteFilter.ALL -> notes
998+
NoteFilter.TEXT_ONLY -> notes.filter { it.noteType == NoteType.TEXT }
980999
NoteFilter.CHECKLIST_ONLY -> notes.filter { it.noteType == NoteType.CHECKLIST }
9811000
}
1001+
return if (colorFilter != null) {
1002+
byType.filter { it.color == colorFilter }
1003+
} else {
1004+
byType
1005+
}
9821006
}
9831007

9841008
/**
@@ -1043,6 +1067,36 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
10431067
Logger.d(TAG, "πŸ” Note filter changed to: ${filter.prefsValue}")
10441068
}
10451069

1070+
/**
1071+
* πŸ†• v2.5.0: Aktiviert/deaktiviert den Farbfilter.
1072+
* @param hex Hex-String der Farbe (z.B. "#F28B82") oder null zum Aufheben.
1073+
*/
1074+
fun setColorFilter(hex: String?) {
1075+
_colorFilter.value = hex
1076+
prefs.edit { putString(Constants.KEY_COLOR_FILTER, hex ?: "") }
1077+
Logger.d(TAG, "🎨 Color filter changed to: ${hex ?: "none"}")
1078+
}
1079+
1080+
/**
1081+
* πŸ†• v2.5.0: Anzahl Notizen pro Farbe nach Typ-Filter (vor Farbfilter).
1082+
* Wird im Farbfilter-Dropdown als Datenquelle verwendet β€” zeigt nur Farben mit count > 0.
1083+
* Map-Key: Hex-String ("#F28B82") oder null (keine Farbe zugewiesen).
1084+
*/
1085+
val availableColors: StateFlow<Map<String?, Int>> = combine(
1086+
_notes, _noteFilter
1087+
) { notes, filter ->
1088+
val byType = when (filter) {
1089+
NoteFilter.ALL -> notes
1090+
NoteFilter.TEXT_ONLY -> notes.filter { it.noteType == NoteType.TEXT }
1091+
NoteFilter.CHECKLIST_ONLY -> notes.filter { it.noteType == NoteType.CHECKLIST }
1092+
}
1093+
byType.groupingBy { it.color }.eachCount()
1094+
}.stateIn(
1095+
scope = viewModelScope,
1096+
started = SharingStarted.WhileSubscribed(5_000),
1097+
initialValue = emptyMap()
1098+
)
1099+
10461100
/**
10471101
* πŸ†• v1.9.0 (F10): Setzt den Suchbegriff (session-only, nicht persistent).
10481102
*/

0 commit comments

Comments
Β (0)