diff --git a/lib/drawer/drawer.dart b/lib/drawer/drawer.dart index b2a4bc1867bd45d18d82aa8b1e229f2a3e3f7d27..8d6ce2b7bfb3dcf154059690ae4a0d4706c04753 100644 --- a/lib/drawer/drawer.dart +++ b/lib/drawer/drawer.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:text_search/text_search.dart'; import '../context.dart'; import '../dialogs/error_dialog.dart'; @@ -15,12 +16,22 @@ class ClassDrawer extends StatefulWidget { } class _ClassDrawerState extends State<ClassDrawer> { + String activeQuery = ''; + late final searchTextController = TextEditingController(text: activeQuery); + List<Class> get availableClasses => widget.appContext.classes ?? []; @override void initState() { super.initState(); fetchLectures().onError((error, _) => _showErrorDialog(error)); + searchTextController.addListener(_onSearchChanged); + } + + @override + void dispose() { + searchTextController.removeListener(_onSearchChanged); + super.dispose(); } void _showErrorDialog(Object? error) { @@ -33,6 +44,58 @@ class _ClassDrawerState extends State<ClassDrawer> { ); } + void _onSearchChanged() { + if (searchTextController.text != activeQuery) { + setState(() { + activeQuery = searchTextController.text; + }); + } + } + + bool _isSelected(int classId) { + return widget.appContext.profile.value!.selectedClasses.contains(classId); + } + + List<Class> getFilteredClasses() { + if (activeQuery.trim().isEmpty) { + return availableClasses; + } + + final searchables = availableClasses + .map((e) => + TextSearchItem(e, [TextSearchItemTerm(e.codeWithoutFaculty)])) + .toList(); + + final search = TextSearch<Class>(searchables); + final results = search.search(activeQuery); + + results.sort((a, b) { + if (a.score == b.score) { + return a.object.codeWithoutFaculty + .compareTo(b.object.codeWithoutFaculty); + } + + return a.score.compareTo(b.score); + }); + + return results.map((e) => e.object).toList(); + } + + List<Class> getFilteredClassesSorted() { + final results = getFilteredClasses(); + + final modifiedResults = + results.where((cls) => _isSelected(cls.id)).toList(); + + for (final cls in results) { + if (!_isSelected(cls.id)) { + modifiedResults.add(cls); + } + } + + return modifiedResults; + } + Future<void> fetchLectures() async { final allLectures = await widget.appContext.untis.allLectures.fetched; widget.appContext.lectures = allLectures; @@ -41,13 +104,30 @@ class _ClassDrawerState extends State<ClassDrawer> { @override Widget build(BuildContext context) { + final filteredClasses = getFilteredClasses(); + return Drawer( - child: ListView.builder( - itemBuilder: (context, index) { - final cls = availableClasses.elementAt(index); - return ClassListItem(cls: cls, appContext: widget.appContext); - }, - itemCount: availableClasses.length, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextField( + controller: searchTextController, + decoration: const InputDecoration( + hintText: 'Suche...', + ), + ), + ), + Expanded( + child: ListView.builder( + itemBuilder: (context, index) { + final cls = filteredClasses.elementAt(index); + return ClassListItem(cls: cls, appContext: widget.appContext); + }, + itemCount: filteredClasses.length, + ), + ), + ], ), ); } diff --git a/lib/untis/class.dart b/lib/untis/class.dart index 17fc89fa742cc621d7cc2da62027e350f6e55efb..fcb380bbe1d598df869a044e1f75168f3cea2b88 100644 --- a/lib/untis/class.dart +++ b/lib/untis/class.dart @@ -5,6 +5,7 @@ class Class extends UntisElement { String get name => json['longName']; String get code => json['name']; + String get codeWithoutFaculty => code.substring(1); Class(super.json); } diff --git a/pubspec.lock b/pubspec.lock index d0aa88d36257b6b15e3b594503b4ea53ef4e10e4..4f970732f47353cf4eda7c4a8b137b96aaf2bb94 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -240,6 +240,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" + text_search: + dependency: "direct main" + description: + name: text_search + sha256: e2e826848ba452df6702dc8f1315de90e812eb2303ecec9cc1155f7715bec934 + url: "https://pub.dev" + source: hosted + version: "1.0.1" + tuple: + dependency: transitive + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4772eba700e70331151dc5f8ede51364def94339..ea707458756109f471a2eef840af3c09f6ed29e3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,6 +37,7 @@ dependencies: cupertino_icons: ^1.0.2 path_provider: ^2.1.1 path: ^1.8.3 + text_search: ^1.0.1 dev_dependencies: flutter_test: