finished multi select actions

This commit is contained in:
Moritz Weber 2022-03-18 20:13:19 +01:00
parent 76a98df75c
commit a7fe60e631
14 changed files with 204 additions and 108 deletions

View file

@ -90,17 +90,22 @@ class DynamicQueue implements ManagedQueueInfo {
}
}
void addToQueue(Song song) {
final queueItem = QueueItemModel(
song as SongModel,
originalIndex: _availableSongs.length, // interference with predecessors/successors?
source: QueueItemSource.added,
isAvailable: false,
);
void addToQueue(List<Song> songs) {
final queueItems = <QueueItem>[];
int i = 0;
for (final song in songs) {
queueItems.add(QueueItemModel(
song as SongModel,
originalIndex: _availableSongs.length + i, // interference with predecessors/successors?
source: QueueItemSource.added,
isAvailable: false,
));
i++;
}
_availableSongs.add(queueItem);
_availableSongs.addAll(queueItems);
_availableSongsSubject.add(_availableSongs);
_queue.add(queueItem);
_queue.addAll(queueItems);
_queueSubject.add(_queue);
}
@ -254,6 +259,16 @@ class DynamicQueue implements ManagedQueueInfo {
return queueItems;
}
int getNextNormalIndex(int index) {
int i = index;
while (_queue[i].source == QueueItemSource.added) {
i++;
}
return i;
}
List<QueueItem> _filterAvailableSongs(List<QueueItem> availableSongs) {
final List<QueueItem> result = [];

View file

@ -47,8 +47,9 @@ abstract class AudioPlayerRepository extends AudioPlayerInfoRepository {
required int initialIndex,
required Playable playable,
});
Future<void> addToQueue(Song song);
Future<void> addToQueue(List<Song> songs);
Future<void> playNext(List<Song> songs);
Future<void> addToNext(List<Song> songs);
Future<void> moveQueueItem(int oldIndex, int newIndex);
Future<void> removeQueueIndex(int index);

View file

@ -55,7 +55,7 @@ abstract class MusicDataRepository extends MusicDataInfoRepository {
Future<void> insertPlaylist(String name);
Future<void> updatePlaylist(int id, String name);
Future<void> removePlaylist(Playlist playlist);
Future<void> appendSongToPlaylist(Playlist playlist, Song song);
Future<void> addSongsToPlaylist(Playlist playlist, List<Song> songs);
Future<void> removePlaylistEntry(int playlistId, int index);
Future<void> movePlaylistEntry(int playlistId, int oldIndex, int newIndex);

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:get_it/get_it.dart';
import '../widgets/bottom_sheet/add_to_playlist.dart';
import '../../domain/entities/album.dart';
import '../../domain/entities/song.dart';
@ -179,18 +180,23 @@ class _AlbumDetailsPageState extends State<AlbumDetailsPage> {
Navigator.of(context, rootNavigator: true).pop();
},
),
const ListTile(
enabled: false,
title: Text('Append to manual queue'),
ListTile(
title: const Text('Append to manually queued songs'),
leading: const Icon(Icons.play_arrow_rounded),
onTap: () {
audioStore.appendToNext(songs);
Navigator.of(context, rootNavigator: true).pop();
},
),
const ListTile(
enabled: false,
title: Text('Add to queue'),
),
const ListTile(
enabled: false,
title: Text('Add to playlist'),
ListTile(
title: const Text('Add to queue'),
leading: const Icon(Icons.queue_rounded),
onTap: () {
audioStore.addToQueue(songs);
Navigator.of(context, rootNavigator: true).pop();
},
),
AddToPlaylistTile(songs: songs, musicDataStore: musicDataStore),
],
);
}),

View file

@ -115,10 +115,12 @@ abstract class _AudioStore with Store {
Future<void> shuffleAll() async => _shuffleAll();
Future<void> addToQueue(Song song) async => _audioPlayerRepository.addToQueue(song);
Future<void> addToQueue(List<Song> songs) async => _audioPlayerRepository.addToQueue(songs);
Future<void> playNext(List<Song> songs) async => _audioPlayerRepository.playNext(songs);
Future<void> appendToNext(List<Song> songs) async => _audioPlayerRepository.addToNext(songs);
Future<void> moveQueueItem(int oldIndex, int newIndex) async =>
_audioPlayerRepository.moveQueueItem(oldIndex, newIndex);

View file

@ -82,8 +82,8 @@ abstract class _MusicDataStore with Store {
_musicDataRepository.updatePlaylist(id, name);
}
Future<void> addSongToPlaylist(Playlist playlist, Song song) async {
_musicDataRepository.appendSongToPlaylist(playlist, song);
Future<void> addSongsToPlaylist(Playlist playlist, List<Song> songs) async {
_musicDataRepository.addSongsToPlaylist(playlist, songs);
}
Future<void> removePlaylistEntry(int playlistId, int index) async {

View file

@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import '../../state/music_data_store.dart';
import '../../../domain/entities/playlist.dart';
import '../../../domain/entities/song.dart';
import '../../theming.dart';
class AddToPlaylistTile extends StatelessWidget {
const AddToPlaylistTile({Key? key, required this.songs, required this.musicDataStore}) : super(key: key);
final List<Song> songs;
final MusicDataStore musicDataStore;
@override
Widget build(BuildContext context) {
return ListTile(
title: const Text('Add to playlist'),
leading: const Icon(Icons.playlist_add_rounded),
onTap: () {
Navigator.of(context, rootNavigator: true).pop();
showDialog(
context: context,
builder: (BuildContext context) {
return Observer(
builder: (context) {
final playlists = musicDataStore.playlistsStream.value ?? [];
return SimpleDialog(
backgroundColor: DARK3,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(HORIZONTAL_PADDING),
child: Container(
height: 300.0,
width: 300.0,
child: ListView.separated(
itemCount: playlists.length,
itemBuilder: (_, int index) {
final Playlist playlist = playlists[index];
return ListTile(
title: Text(playlist.name),
onTap: () {
musicDataStore.addSongsToPlaylist(playlist, songs);
Navigator.pop(context);
},
);
},
separatorBuilder: (BuildContext context, int index) => const SizedBox(
height: 4.0,
),
),
),
),
SimpleDialogOption(
onPressed: () {
Navigator.pop(context);
},
child: const Text(
'Cancel',
textAlign: TextAlign.right,
),
),
],
);
},
);
},
);
},
);
}
}

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:get_it/get_it.dart';
import 'bottom_sheet/add_to_playlist.dart';
import '../../domain/entities/album.dart';
import '../../domain/entities/artist.dart';
@ -254,72 +255,21 @@ class _SongBottomSheetState extends State<SongBottomSheet> {
ListTile(
title: const Text('Append to manually queued songs'),
leading: const Icon(Icons.play_arrow_rounded),
onTap: () {},
enabled: false,
onTap: () {
audioStore.appendToNext([song]);
Navigator.of(context, rootNavigator: true).pop();
},
),
ListTile(
title: const Text('Add to queue'),
leading: const Icon(Icons.queue_rounded),
onTap: () {
audioStore.addToQueue(song);
audioStore.addToQueue([song]);
Navigator.of(context, rootNavigator: true).pop();
},
),
],
ListTile(
title: const Text('Add to playlist'),
leading: const Icon(Icons.playlist_add_rounded),
onTap: () {
Navigator.of(context, rootNavigator: true).pop();
showDialog(
context: context,
builder: (BuildContext context) {
return Observer(
builder: (context) {
final playlists = musicDataStore.playlistsStream.value ?? [];
return SimpleDialog(
backgroundColor: DARK3,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(HORIZONTAL_PADDING),
child: Container(
height: 300.0,
width: 300.0,
child: ListView.separated(
itemCount: playlists.length,
itemBuilder: (_, int index) {
final Playlist playlist = playlists[index];
return ListTile(
title: Text(playlist.name),
onTap: () {
musicDataStore.addSongToPlaylist(playlist, song);
Navigator.pop(context);
},
);
},
separatorBuilder: (BuildContext context, int index) => const SizedBox(
height: 4.0,
),
),
),
),
SimpleDialogOption(
onPressed: () {
Navigator.pop(context);
},
child: const Text(
'Cancel',
textAlign: TextAlign.right,
),
),
],
);
},
);
},
);
},
),
AddToPlaylistTile(songs: [song], musicDataStore: musicDataStore),
];
return MyBottomSheet(widgets: widgets);

View file

@ -22,7 +22,8 @@ abstract class AudioPlayerDataSource {
required List<SongModel> queue,
required int initialIndex,
});
Future<void> addToQueue(SongModel song);
Future<void> addToQueue(List<SongModel> songs);
Future<void> insertIntoQueue(List<SongModel> songs, int index);
Future<void> moveQueueItem(int oldIndex, int newIndex);
Future<void> playNext(List<SongModel> songs);
Future<void> removeQueueIndex(int index);

View file

@ -48,7 +48,9 @@ class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
late ja.ConcatenatingAudioSource _audioSource;
late List<SongModel> _queue;
// inclusive
late int _loadStartIndex;
// exclusive
late int _loadEndIndex;
bool isQueueLoaded = false;
@ -142,16 +144,17 @@ class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
}
@override
Future<void> addToQueue(SongModel song) async {
_queue.add(song);
Future<void> addToQueue(List<SongModel> songs) async {
_queue.addAll(songs);
if (_loadStartIndex < _loadEndIndex) {
if (_loadEndIndex == _queue.length) {
if (_loadEndIndex == _queue.length - 1) {
// queue is loaded until the end -> load this song too and adapt the index
_loadEndIndex++;
await _audioSource.add(ja.AudioSource.uri(Uri.file(song.path)));
await _audioSource.addAll(songs.map((e) => ja.AudioSource.uri(Uri.file(e.path))).toList());
}
} else {
await _audioSource.add(ja.AudioSource.uri(Uri.file(song.path)));
await _audioSource.addAll(songs.map((e) => ja.AudioSource.uri(Uri.file(e.path))).toList());
}
}
@ -197,6 +200,27 @@ class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
songs.map((e) => ja.AudioSource.uri(Uri.file(e.path))).toList());
}
@override
Future<void> insertIntoQueue(List<SongModel> songs, int index) async {
_queue.insertAll(index, songs);
final sourceIndex = _calcSourceIndex(index);
final isIndexLoaded = _isQueueIndexInLoadInterval(index);
if (index < _loadStartIndex) {
_loadStartIndex = _loadStartIndex + songs.length;
}
if (index < _loadEndIndex) {
_loadEndIndex = _loadEndIndex + songs.length;
}
if (isIndexLoaded) {
await _audioSource.insertAll(sourceIndex,
songs.map((e) => ja.AudioSource.uri(Uri.file(e.path))).toList());
} else {
_updateCurrentIndex(_audioPlayer.currentIndex);
}
}
@override
Future<void> removeQueueIndex(int index) async {
_log.d('removeQueueIndex: $index');
@ -221,7 +245,7 @@ class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
_updateLoadedQueue(currentSourceIndex);
}
} else {
_updateCurrentIndex(_audioPlayer.currentIndex!);
_updateCurrentIndex(_audioPlayer.currentIndex);
}
}
@ -269,7 +293,8 @@ class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
List<SongModel> _getQueueToLoad(List<SongModel> queue, int initialIndex) {
if (queue.length > LOAD_MAX) {
_loadStartIndex = (initialIndex - LOAD_INTERVAL) % queue.length;
_loadEndIndex = (initialIndex + LOAD_INTERVAL + 1) % queue.length;
_loadEndIndex = initialIndex + LOAD_INTERVAL + 1;
if (_loadEndIndex > queue.length) _loadEndIndex %= queue.length;
List<SongModel> smallQueue;
if (_loadStartIndex < _loadEndIndex) {
@ -308,6 +333,8 @@ class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
}
}
/// update loaded queue in case the loaded queue is one continous part of the queue
/// return true if queue was changed
Future<bool> _updateLoadedQueueBaseCase(int newIndex) async {
if (newIndex < LOAD_INTERVAL) {
// nearing the start of the loaded songs
@ -344,6 +371,8 @@ class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
return false;
}
/// update loaded queue in case the loaded queue is split in two parts
/// return true if queue was changed
Future<bool> _updateLoadedQueueInverted(int newIndex) async {
final rightOfLoadEnd = newIndex >= _loadEndIndex;

View file

@ -11,23 +11,35 @@ import '../playlist_data_source.dart';
part 'playlist_dao.g.dart';
@DriftAccessor(tables: [Albums, Artists, Songs, Playlists, PlaylistEntries, SmartLists, SmartListArtists])
@DriftAccessor(
tables: [Albums, Artists, Songs, Playlists, PlaylistEntries, SmartLists, SmartListArtists])
class PlaylistDao extends DatabaseAccessor<MoorDatabase>
with _$PlaylistDaoMixin
implements PlaylistDataSource {
PlaylistDao(MoorDatabase db) : super(db);
@override
Future<void> appendSongToPlaylist(PlaylistModel playlist, SongModel song) async {
Future<void> addSongsToPlaylist(PlaylistModel playlist, List<SongModel> songs) async {
final plSongs =
await (select(playlistEntries)..where((tbl) => tbl.playlistId.equals(playlist.id))).get();
final songCount = plSongs.length;
into(playlistEntries).insert(PlaylistEntriesCompanion(
playlistId: Value(playlist.id),
songPath: Value(song.path),
position: Value(songCount),
));
int i = 0;
final entries = <PlaylistEntriesCompanion>[];
for (final s in songs) {
entries.add(PlaylistEntriesCompanion(
playlistId: Value(playlist.id),
songPath: Value(s.path),
position: Value(songCount + i),
));
i++;
}
await batch((batch) {
batch.insertAll(playlistEntries, entries);
});
}
@override

View file

@ -10,7 +10,7 @@ abstract class PlaylistDataSource {
Future<void> insertPlaylist(String name);
Future<void> updatePlaylist(int id, String name);
Future<void> removePlaylist(PlaylistModel playlist);
Future<void> appendSongToPlaylist(PlaylistModel playlist, SongModel song);
Future<void> addSongsToPlaylist(PlaylistModel playlist, List<SongModel> songs);
Future<void> removeIndex(int playlistId, int index);
Future<void> moveEntry(int playlistId, int oldIndex, int newIndex);

View file

@ -25,11 +25,9 @@ class AudioPlayerRepositoryImpl implements AudioPlayerRepository {
if (!_blockIndexUpdate) {
_updateCurrentSong(queueStream.value, index);
}
_dynamicQueue.updateCurrentIndex(index).then((value) {
if (value.isNotEmpty) {
for (final s in value) {
_audioPlayerDataSource.addToQueue(s as SongModel);
}
_dynamicQueue.updateCurrentIndex(index).then((songs) {
if (songs.isNotEmpty) {
_audioPlayerDataSource.addToQueue(songs.map((e) => e as SongModel).toList());
_queueSubject.add(_dynamicQueue.queue);
}
});
@ -88,10 +86,9 @@ class AudioPlayerRepositoryImpl implements AudioPlayerRepository {
ManagedQueueInfo get managedQueueInfo => _dynamicQueue;
@override
Future<void> addToQueue(Song song) async {
_log.d('addToQueue');
_audioPlayerDataSource.addToQueue(song as SongModel);
_dynamicQueue.addToQueue(song);
Future<void> addToQueue(List<Song> songs) async {
_audioPlayerDataSource.addToQueue(songs.map((e) => e as SongModel).toList());
_dynamicQueue.addToQueue(songs);
_queueSubject.add(_dynamicQueue.queue);
}
@ -177,6 +174,16 @@ class AudioPlayerRepositoryImpl implements AudioPlayerRepository {
_queueSubject.add(_dynamicQueue.queue);
}
@override
Future<void> addToNext(List<Song> songs) async {
final index = _dynamicQueue.getNextNormalIndex(currentIndexStream.value + 1);
_audioPlayerDataSource.insertIntoQueue(songs.map((e) => e as SongModel).toList(), index);
_dynamicQueue.insertIntoQueue(songs, index);
_queueSubject.add(_dynamicQueue.queue);
}
@override
Future<void> removeQueueIndex(int index) async {
_removeQueueIndex(index, true);

View file

@ -343,8 +343,9 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
}
@override
Future<void> appendSongToPlaylist(Playlist playlist, Song song) async {
_playlistDataSource.appendSongToPlaylist(playlist as PlaylistModel, song as SongModel);
Future<void> addSongsToPlaylist(Playlist playlist, List<Song> songs) async {
_playlistDataSource.addSongsToPlaylist(
playlist as PlaylistModel, songs.map((e) => e as SongModel).toList());
}
@override