Skip to content

Commit e3c2d33

Browse files
mikebarkminCopilot
andcommitted
feat(notes): add search bar to global notes view
Tap the search icon in the AppBar to filter notes by body text across all tabs. The X button clears the query and returns to normal title. Empty-state shows 'no_notes_found' when no notes match the query. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent ec3533e commit e3c2d33

3 files changed

Lines changed: 81 additions & 6 deletions

File tree

assets/translations/de.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@
194194
"no_session_notes": "Für diese Stunde gibt es noch keine Notizen.",
195195
"no_students_found": "Keine Schüler:innen passen zur aktuellen Suche.",
196196
"no_webdav_backups_found": "Auf dem Server wurden keine WebDAV-Backups gefunden.",
197+
"available_backups": "Verfügbare Backups",
197198
"note_body": "Notiz",
198199
"notes": "Notizen",
199200
"ok": "OK",
@@ -247,6 +248,8 @@
247248
"set_as_default": "Als Standard festlegen",
248249
"list_view": "Liste",
249250
"search_students": "Schüler:innen suchen",
251+
"search_notes": "Notizen suchen",
252+
"no_notes_found": "Keine Notizen passen zur Suche.",
250253
"select_all_students": "Alle auswählen",
251254
"selected_students": "{count} ausgewählt",
252255
"session_label": "Bezeichnung",

assets/translations/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@
194194
"no_session_notes": "No notes for this lesson yet.",
195195
"no_students_found": "No students match the current search.",
196196
"no_webdav_backups_found": "No WebDAV backups were found on the server.",
197+
"available_backups": "Available backups",
197198
"note_body": "Note",
198199
"notes": "Notes",
199200
"ok": "OK",
@@ -247,6 +248,8 @@
247248
"set_as_default": "Set as default",
248249
"list_view": "List",
249250
"search_students": "Search students",
251+
"search_notes": "Search notes",
252+
"no_notes_found": "No notes match the search.",
250253
"select_all_students": "Select all",
251254
"selected_students": "{count} selected",
252255
"session_label": "Session label",

lib/features/notes/notes_screen.dart

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,26 @@ class NotesScreen extends ConsumerStatefulWidget {
4848
}
4949

5050
class _NotesScreenState extends ConsumerState<NotesScreen> {
51+
bool _searching = false;
52+
final _searchController = TextEditingController();
53+
String _searchQuery = '';
54+
55+
@override
56+
void dispose() {
57+
_searchController.dispose();
58+
super.dispose();
59+
}
60+
61+
void _startSearch() => setState(() => _searching = true);
62+
63+
void _stopSearch() {
64+
setState(() {
65+
_searching = false;
66+
_searchQuery = '';
67+
_searchController.clear();
68+
});
69+
}
70+
5171
@override
5272
Widget build(BuildContext context) {
5373
final groupsValue = ref.watch(activeGroupsProvider);
@@ -57,7 +77,29 @@ class _NotesScreenState extends ConsumerState<NotesScreen> {
5777
length: 4,
5878
child: Scaffold(
5979
appBar: AppBar(
60-
title: Text('notes'.tr()),
80+
title: _searching
81+
? TextField(
82+
controller: _searchController,
83+
autofocus: true,
84+
decoration: InputDecoration(
85+
hintText: 'search_notes'.tr(),
86+
border: InputBorder.none,
87+
),
88+
onChanged: (v) => setState(() => _searchQuery = v),
89+
)
90+
: Text('notes'.tr()),
91+
actions: [
92+
if (_searching)
93+
IconButton(
94+
icon: const Icon(Icons.close),
95+
onPressed: _stopSearch,
96+
)
97+
else
98+
IconButton(
99+
icon: const Icon(Icons.search),
100+
onPressed: _startSearch,
101+
),
102+
],
61103
bottom: TabBar(
62104
tabs: [
63105
Tab(text: 'all'.tr()),
@@ -80,13 +122,26 @@ class _NotesScreenState extends ConsumerState<NotesScreen> {
80122
: null,
81123
body: TabBarView(
82124
children: [
83-
_NotesList(provider: allNotesProvider, emptyKey: 'empty_notes'),
84-
_NotesList(provider: todoNotesProvider, emptyKey: 'empty_notes'),
85-
_NotesList(provider: doneNotesProvider, emptyKey: 'empty_notes'),
125+
_NotesList(
126+
provider: allNotesProvider,
127+
emptyKey: 'empty_notes',
128+
searchQuery: _searchQuery,
129+
),
130+
_NotesList(
131+
provider: todoNotesProvider,
132+
emptyKey: 'empty_notes',
133+
searchQuery: _searchQuery,
134+
),
135+
_NotesList(
136+
provider: doneNotesProvider,
137+
emptyKey: 'empty_notes',
138+
searchQuery: _searchQuery,
139+
),
86140
_NotesList(
87141
provider: archivedNotesProvider,
88142
emptyKey: 'empty_notes',
89143
archived: true,
144+
searchQuery: _searchQuery,
90145
),
91146
],
92147
),
@@ -125,11 +180,13 @@ class _NotesList extends ConsumerWidget {
125180
required this.provider,
126181
required this.emptyKey,
127182
this.archived = false,
183+
this.searchQuery = '',
128184
});
129185

130186
final StreamProvider<List<TeacherNote>> provider;
131187
final String emptyKey;
132188
final bool archived;
189+
final String searchQuery;
133190

134191
@override
135192
Widget build(BuildContext context, WidgetRef ref) {
@@ -139,11 +196,23 @@ class _NotesList extends ConsumerWidget {
139196

140197
return ContentConstraints(
141198
child: notesValue.when(
142-
data: (notes) {
199+
data: (allNotes) {
200+
final notes = searchQuery.isEmpty
201+
? allNotes
202+
: allNotes
203+
.where(
204+
(n) => n.body.toLowerCase().contains(
205+
searchQuery.toLowerCase(),
206+
),
207+
)
208+
.toList(growable: false);
209+
143210
if (notes.isEmpty) {
144211
return EmptyState(
145212
icon: Icons.sticky_note_2_outlined,
146-
title: emptyKey.tr(),
213+
title: searchQuery.isEmpty
214+
? emptyKey.tr()
215+
: 'no_notes_found'.tr(),
147216
);
148217
}
149218

0 commit comments

Comments
 (0)