Skip to content

Commit

Permalink
General UI & Bug Fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
WindingMotor committed Sep 23, 2024
1 parent dce8b9f commit 82f2cae
Show file tree
Hide file tree
Showing 8 changed files with 353 additions and 175 deletions.
34 changes: 28 additions & 6 deletions lib/audio/nplayer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ class NPlayer extends ChangeNotifier {
StreamController<void>.broadcast();
Stream<void> get songChangeStream => _songChangeController.stream;

Timer? _debounceTimer;
final Duration _debounceDuration = Duration(milliseconds: 300);

void notifySongChange() {
_songChangeController.add(null);
}
Expand Down Expand Up @@ -659,13 +662,26 @@ class NPlayer extends ChangeNotifier {
return _allSongs.where((song) => songNames.contains(song.title)).toList();
}

Future<void> refreshPlaylists() async {
await PlaylistManager.load();
notifyListeners();
}

// SECTION: Search and Sort
void setSearchQuery(String query) {
void setSearchQuery(String query) {
_log("Setting search query: $query");
_searchQuery = query.toLowerCase();
_filterAndSortSongs();

// Cancel the previous timer if it exists
_debounceTimer?.cancel();

// Start a new timer
_debounceTimer = Timer(_debounceDuration, () {
_filterAndSortSongs();
});
}


void _filterAndSortSongs() {
_log("Filtering and sorting songs");
if (_searchQuery.isEmpty) {
Expand All @@ -678,20 +694,25 @@ class NPlayer extends ChangeNotifier {
WeightedKey(
name: 'title',
getter: (Music song) => song.title.toLowerCase(),
weight: 8),
weight: 60),
WeightedKey(
name: 'artist',
getter: (Music song) => song.artist.toLowerCase(),
weight: 4),
weight: 30),
/*
WeightedKey(
name: 'album',
getter: (Music song) => song.album.toLowerCase(),
weight: 5),
weight: 20),
WeightedKey(
name: 'genre',
getter: (Music song) => song.genre.toLowerCase(),
weight: 1),
weight: 10),
*/
],

threshold: 0.6,
distance: 25,
),
);

Expand All @@ -704,6 +725,7 @@ class NPlayer extends ChangeNotifier {
notifyListeners();
}


void _applySorting() {
_log("Applying sorting: by $_sortBy, ascending: $_sortAscending");
_sortedSongs.sort((a, b) {
Expand Down
35 changes: 27 additions & 8 deletions lib/audio/nplaylist.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,27 @@ class PlaylistManager {
return List<String>.from(_playlists[playlistName]?['songs'] ?? []);
}

static String? getPlaylistImagePath(String playlistName) {
return _playlists[playlistName]?['imagePath'];
static String? getPlaylistImagePath(String playlistName) {
String? imagePath = _playlists[playlistName]?['imagePath'];
if (imagePath != null && File(imagePath).existsSync()) {
return imagePath;
} else {
// Try to find an image with the playlist name in the _playlistArtDir
Directory dir = Directory(_playlistArtDir);
if (dir.existsSync()) {
List<FileSystemEntity> files = dir.listSync();
for (var file in files) {
if (file is File && path.basenameWithoutExtension(file.path) == playlistName) {
_playlists[playlistName]!['imagePath'] = file.path;
save(); // Update the saved playlist data
return file.path;
}
}
}
}
print ('No image found for playlist: $playlistName');
return null;
}

static Future<void> createPlaylist(String name, {String? imagePath}) async {
if (!_playlists.containsKey(name)) {
Expand Down Expand Up @@ -72,10 +90,11 @@ class PlaylistManager {
}
}

static Future<void> setPlaylistImage(String playlistName, File imageFile) async {
String newImagePath = path.join(_playlistArtDir, '$playlistName${path.extension(imageFile.path)}');
await imageFile.copy(newImagePath);
_playlists[playlistName]!['imagePath'] = newImagePath;
await save();
}
static Future<void> setPlaylistImage(String playlistName, File imageFile) async {
String extension = path.extension(imageFile.path);
String newImagePath = path.join(_playlistArtDir, '$playlistName$extension');
await imageFile.copy(newImagePath);
_playlists[playlistName]!['imagePath'] = newImagePath;
await save();
}
}
209 changes: 169 additions & 40 deletions lib/custom/custom_song_list_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ class SongListBuilder extends StatefulWidget {
final bool isPlayingList;
final void Function(Music)? onTap;
final bool isPlaylist;
final Orientation? orientation;

const SongListBuilder({
Key? key,
required this.songs,
this.isPlayingList = false,
this.onTap,
this.isPlaylist = false,
required this.orientation,
}) : super(key: key);

@override
Expand All @@ -33,50 +35,82 @@ class _SongListBuilderState extends State<SongListBuilder> {
}

@override
Widget build(BuildContext context) {
return Consumer<NPlayer>(
builder: (context, player, child) {
return Scrollbar(
controller: _scrollController,
thumbVisibility: true, // Always show the scrollbar
child: ListView.builder(
controller: _scrollController,
itemCount: widget.songs.length + (selectedSongs.isNotEmpty ? 1 : 0),
itemBuilder: (context, index) {
if (index == lastSelectedIndex && selectedSongs.isNotEmpty) {
return Column(
children: [
_SongListTile(
song: widget.songs[index],
isCurrentSong:
widget.songs[index] == player.getCurrentSong(),
isSelected: selectedSongs.contains(widget.songs[index]),
player: player,
onTap: _handleTap,
onLongPress: () => _handleLongPress(index),
),
_buildPlaylistCard(player),
],
);
} else if (index < widget.songs.length) {
return _SongListTile(
song: widget.songs[index],
isCurrentSong: widget.songs[index] == player.getCurrentSong(),
isSelected: selectedSongs.contains(widget.songs[index]),
player: player,
onTap: _handleTap,
onLongPress: () => _handleLongPress(index),
);
} else {
return _buildPlaylistCard(player);
}
},
),
);
Widget build(BuildContext context) {
return Consumer<NPlayer>(
builder: (context, player, child) {
return Scrollbar(
controller: _scrollController,
thumbVisibility: true,
child: widget.orientation == Orientation.landscape
? _buildGridView(player)
: _buildListView(player),
);
},
);
}

Widget _buildListView(NPlayer player) {
return ListView.builder(
controller: _scrollController,
itemCount: widget.songs.length + (selectedSongs.isNotEmpty ? 1 : 0),
itemBuilder: (context, index) {
if (index == lastSelectedIndex && selectedSongs.isNotEmpty) {
return Column(
children: [
_SongListTile(
song: widget.songs[index],
isCurrentSong: widget.songs[index] == player.getCurrentSong(),
isSelected: selectedSongs.contains(widget.songs[index]),
player: player,
onTap: _handleTap,
onLongPress: () => _handleLongPress(index),
),
_buildPlaylistCard(player),
],
);
} else if (index < widget.songs.length) {
return _SongListTile(
song: widget.songs[index],
isCurrentSong: widget.songs[index] == player.getCurrentSong(),
isSelected: selectedSongs.contains(widget.songs[index]),
player: player,
onTap: _handleTap,
onLongPress: () => _handleLongPress(index),
);
} else {
return _buildPlaylistCard(player);
}
},
);
}

Widget _buildGridView(NPlayer player) {
return GridView.builder(
controller: _scrollController,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4, // Increase to 4 columns
childAspectRatio: 2.5, // Adjust for a more compact layout
crossAxisSpacing: 8, // Add some horizontal spacing
mainAxisSpacing: 8, // Add some vertical spacing
),
itemCount: widget.songs.length + (selectedSongs.isNotEmpty ? 1 : 0),
itemBuilder: (context, index) {
if (index < widget.songs.length) {
return _SongGridTile(
song: widget.songs[index],
isCurrentSong: widget.songs[index] == player.getCurrentSong(),
isSelected: selectedSongs.contains(widget.songs[index]),
player: player,
onTap: _handleTap,
onLongPress: () => _handleLongPress(index),
);
} else {
return _buildPlaylistCard(player);
}
},
);
}

void _handleTap(Music song) {
if (selectedSongs.isEmpty) {
if (widget.onTap != null) {
Expand Down Expand Up @@ -259,3 +293,98 @@ class _SongListTile extends StatelessWidget {
);
}
}

class _SongGridTile extends StatelessWidget {
final Music song;
final bool isCurrentSong;
final bool isSelected;
final NPlayer player;
final Function(Music) onTap;
final VoidCallback onLongPress;

const _SongGridTile({
Key? key,
required this.song,
required this.isCurrentSong,
required this.isSelected,
required this.player,
required this.onTap,
required this.onLongPress,
}) : super(key: key);


@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final textTheme = theme.textTheme;

return Card(
color: isSelected
? theme.colorScheme.secondary.withOpacity(0.2)
: theme.cardColor,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
child: InkWell(
onTap: () => onTap(song),
onLongPress: onLongPress,
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: SizedBox(
width: 40,
height: 40,
child: song.picture != null
? Image.memory(song.picture!, fit: BoxFit.cover)
: Container(
color: theme.colorScheme.surface,
child: Icon(Icons.music_note,
color: theme.colorScheme.onSurface, size: 20),
),
),
),
SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
song.title,
style: textTheme.bodySmall?.copyWith(
fontWeight: isCurrentSong ? FontWeight.bold : FontWeight.normal,
color: isCurrentSong
? theme.colorScheme.secondary
: theme.colorScheme.onSurface,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
song.artist,
style: textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurface.withOpacity(0.6),
fontSize: 10,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
],
),
),
),
);
}
}
Loading

0 comments on commit 82f2cae

Please sign in to comment.