finished multi select actions
This commit is contained in:
parent
76a98df75c
commit
a7fe60e631
14 changed files with 204 additions and 108 deletions
|
@ -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 = [];
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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),
|
||||
],
|
||||
);
|
||||
}),
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
72
lib/presentation/widgets/bottom_sheet/add_to_playlist.dart
Normal file
72
lib/presentation/widgets/bottom_sheet/add_to_playlist.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue