✨ Library able to search tracks
This commit is contained in:
@@ -46,6 +46,7 @@ class LibraryScreen extends HookConsumerWidget {
|
|||||||
// Let's assume we use StreamBuilder for now to avoid creating another file/provider on the fly.
|
// Let's assume we use StreamBuilder for now to avoid creating another file/provider on the fly.
|
||||||
final repo = ref.watch(trackRepositoryProvider.notifier);
|
final repo = ref.watch(trackRepositoryProvider.notifier);
|
||||||
final selectedTrackIds = useState<Set<int>>({});
|
final selectedTrackIds = useState<Set<int>>({});
|
||||||
|
final searchQuery = useState<String>('');
|
||||||
final isSelectionMode = selectedTrackIds.value.isNotEmpty;
|
final isSelectionMode = selectedTrackIds.value.isNotEmpty;
|
||||||
|
|
||||||
void toggleSelection(int id) {
|
void toggleSelection(int id) {
|
||||||
@@ -180,13 +181,60 @@ class LibraryScreen extends HookConsumerWidget {
|
|||||||
return const Center(child: Text('No tracks yet. Add some!'));
|
return const Center(child: Text('No tracks yet. Add some!'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ListView.builder(
|
List<Track> filteredTracks;
|
||||||
|
if (searchQuery.value.isEmpty) {
|
||||||
|
filteredTracks = tracks;
|
||||||
|
} else {
|
||||||
|
final query = searchQuery.value.toLowerCase();
|
||||||
|
filteredTracks = tracks.where((track) {
|
||||||
|
if (track.title.toLowerCase().contains(query)) return true;
|
||||||
|
if (track.artist?.toLowerCase().contains(query) ?? false)
|
||||||
|
return true;
|
||||||
|
if (track.album?.toLowerCase().contains(query) ?? false)
|
||||||
|
return true;
|
||||||
|
if (track.lyrics != null) {
|
||||||
|
try {
|
||||||
|
final lyricsData = LyricsData.fromJsonString(
|
||||||
|
track.lyrics!,
|
||||||
|
);
|
||||||
|
for (final line in lyricsData.lines) {
|
||||||
|
if (line.text.toLowerCase().contains(query))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore parsing errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filteredTracks.isEmpty && searchQuery.value.isNotEmpty) {
|
||||||
|
return const Center(
|
||||||
|
child: Text('No tracks match your search.'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: TextField(
|
||||||
|
onChanged: (value) => searchQuery.value = value,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: 'Search tracks...',
|
||||||
|
prefixIcon: Icon(Icons.search),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
bottom: 72 + MediaQuery.paddingOf(context).bottom,
|
bottom: 72 + MediaQuery.paddingOf(context).bottom,
|
||||||
),
|
),
|
||||||
itemCount: tracks.length,
|
itemCount: filteredTracks.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final track = tracks[index];
|
final track = filteredTracks[index];
|
||||||
final isSelected = selectedTrackIds.value.contains(
|
final isSelected = selectedTrackIds.value.contains(
|
||||||
track.id,
|
track.id,
|
||||||
);
|
);
|
||||||
@@ -220,7 +268,10 @@ class LibraryScreen extends HookConsumerWidget {
|
|||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
padding: const EdgeInsets.only(right: 20),
|
padding: const EdgeInsets.only(right: 20),
|
||||||
child: const Icon(Icons.delete, color: Colors.white),
|
child: const Icon(
|
||||||
|
Icons.delete,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
confirmDismiss: (direction) async {
|
confirmDismiss: (direction) async {
|
||||||
return await showDialog(
|
return await showDialog(
|
||||||
@@ -255,7 +306,9 @@ class LibraryScreen extends HookConsumerWidget {
|
|||||||
.read(trackRepositoryProvider.notifier)
|
.read(trackRepositoryProvider.notifier)
|
||||||
.deleteTrack(track.id);
|
.deleteTrack(track.id);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text('Deleted "${track.title}"')),
|
SnackBar(
|
||||||
|
content: Text('Deleted "${track.title}"'),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
@@ -267,7 +320,9 @@ class LibraryScreen extends HookConsumerWidget {
|
|||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
image: track.artUri != null
|
image: track.artUri != null
|
||||||
? DecorationImage(
|
? DecorationImage(
|
||||||
image: FileImage(File(track.artUri!)),
|
image: FileImage(
|
||||||
|
File(track.artUri!),
|
||||||
|
),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
@@ -309,6 +364,9 @@ class LibraryScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user