added play-, like- and skipCount

This commit is contained in:
Moritz Weber 2021-01-09 19:55:56 +01:00
parent a4d7a0071e
commit 58e2c7be81
15 changed files with 235 additions and 52 deletions

View file

@ -41,5 +41,16 @@ class Song extends Equatable {
final int trackNumber;
@override
List<Object> get props => [title, album, artist, blocked];
List<Object> get props => [
path,
title,
album,
artist,
blocked,
next,
previous,
likeCount,
playCount,
skipCount,
];
}

View file

@ -0,0 +1,16 @@
import '../entities/song.dart';
abstract class MusicDataModifierRepository {
Future<void> setSongBlocked(Song song, bool blocked);
Future<void> toggleNextSongLink(Song song);
Future<void> incrementSkipCount(Song song);
Future<void> resetSkipCount(Song song);
Future<void> incrementLikeCount(Song song);
Future<void> decrementLikeCount(Song song);
Future<void> resetLikeCount(Song song);
Future<void> incrementPlayCount(Song song);
Future<void> resetPlayCount(Song song);
}

View file

@ -12,7 +12,4 @@ abstract class MusicDataRepository {
Stream<List<Artist>> get artistStream;
Future<void> updateDatabase();
Future<void> setSongBlocked(Song song, bool blocked);
Future<void> toggleNextSongLink(Song song);
}

View file

@ -5,6 +5,7 @@ import 'package:get_it/get_it.dart';
import 'package:just_audio/just_audio.dart' as ja;
import 'domain/repositories/audio_repository.dart';
import 'domain/repositories/music_data_modifier_repository.dart';
import 'domain/repositories/music_data_repository.dart';
import 'domain/repositories/persistent_player_state_repository.dart';
import 'domain/repositories/settings_repository.dart';
@ -24,6 +25,7 @@ import 'system/datasources/music_data_source_contract.dart';
import 'system/datasources/player_state_data_source.dart';
import 'system/datasources/settings_data_source.dart';
import 'system/repositories/audio_repository_impl.dart';
import 'system/repositories/music_data_modifier_repository_impl.dart';
import 'system/repositories/music_data_repository_impl.dart';
import 'system/repositories/persistent_player_state_repository_impl.dart';
import 'system/repositories/settings_repository_impl.dart';
@ -39,6 +41,7 @@ Future<void> setupGetIt() async {
final musicDataStore = MusicDataStore(
musicDataRepository: getIt(),
settingsRepository: getIt(),
musicDataModifierRepository: getIt(),
);
return musicDataStore;
},
@ -66,15 +69,26 @@ Future<void> setupGetIt() async {
getIt(),
),
);
getIt.registerLazySingleton<MusicDataModifierRepository>(
() => MusicDataModifierRepositoryImpl(
getIt(),
),
);
getIt.registerLazySingleton<AudioRepository>(
() => AudioRepositoryImpl(
getIt(),
),
);
getIt.registerLazySingleton<PlayerStateRepository>(
() => PlayerStateRepositoryImpl(getIt()),
() => PlayerStateRepositoryImpl(
getIt(),
),
);
getIt.registerLazySingleton<SettingsRepository>(
() => SettingsRepositoryImpl(
getIt(),
),
);
getIt.registerLazySingleton<SettingsRepository>(() => SettingsRepositoryImpl(getIt()));
// data sources
final MoorDatabase moorDatabase = MoorDatabase();

View file

@ -4,6 +4,7 @@ import 'package:mobx/mobx.dart';
import '../../domain/entities/album.dart';
import '../../domain/entities/artist.dart';
import '../../domain/entities/song.dart';
import '../../domain/repositories/music_data_modifier_repository.dart';
import '../../domain/repositories/music_data_repository.dart';
import '../../domain/repositories/settings_repository.dart';
@ -13,17 +14,20 @@ class MusicDataStore extends _MusicDataStore with _$MusicDataStore {
MusicDataStore({
@required MusicDataRepository musicDataRepository,
@required SettingsRepository settingsRepository,
}) : super(musicDataRepository, settingsRepository);
@required MusicDataModifierRepository musicDataModifierRepository,
}) : super(musicDataRepository, settingsRepository, musicDataModifierRepository);
}
abstract class _MusicDataStore with Store {
_MusicDataStore(this._musicDataRepository, this._settingsRepository) {
_MusicDataStore(
this._musicDataRepository, this._settingsRepository, this._musicDataModifierRepository) {
songStream = _musicDataRepository.songStream.asObservable(initialValue: []);
albumStream = _musicDataRepository.albumStream.asObservable(initialValue: []);
artistStream = _musicDataRepository.artistStream.asObservable(initialValue: []);
}
final MusicDataRepository _musicDataRepository;
final MusicDataModifierRepository _musicDataModifierRepository;
final SettingsRepository _settingsRepository;
@observable
@ -51,7 +55,6 @@ abstract class _MusicDataStore with Store {
isUpdatingDatabase = false;
}
@action
Future<void> fetchSongsFromAlbum(Album album) async {
albumSongStream = _musicDataRepository.getAlbumSongStream(album).asObservable(initialValue: []);
@ -64,13 +67,18 @@ abstract class _MusicDataStore with Store {
}
Future<void> setSongBlocked(Song song, bool blocked) async {
await _musicDataRepository.setSongBlocked(song, blocked);
await _musicDataModifierRepository.setSongBlocked(song, blocked);
}
Future<void> toggleNextSongLink(Song song) async {
await _musicDataRepository.toggleNextSongLink(song);
await _musicDataModifierRepository.toggleNextSongLink(song);
}
Future<void> incrementLikeCount(Song song) =>
_musicDataModifierRepository.incrementLikeCount(song);
Future<void> resetLikeCount(Song song) => _musicDataModifierRepository.resetLikeCount(song);
Future<void> addLibraryFolder(String path) async {
await _settingsRepository.addLibraryFolder(path);
}

View file

@ -40,7 +40,7 @@ class AlbumArt extends StatelessWidget {
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0x00000000), Color(0xCC000000)],
colors: [Color(0x00000000), Color(0xBB000000)],
stops: [0.0, 1.0],
),
),
@ -64,22 +64,32 @@ class AlbumArt extends StatelessWidget {
shadows: [
const Shadow(
blurRadius: 2.0,
color: Colors.black45,
color: Colors.black87,
offset: Offset(.5, .5),
),
const Shadow(
blurRadius: 8.0,
color: Colors.black54,
offset: Offset(0, 0),
),
],
),
),
Text(
song.artist,
style: TEXT_SUBTITLE.copyWith(
color: Colors.grey[300],
color: Colors.grey[100],
shadows: [
const Shadow(
blurRadius: 2.0,
color: Colors.black45,
color: Colors.black87,
offset: Offset(.5, .5),
),
const Shadow(
blurRadius: 8.0,
color: Colors.black54,
offset: Offset(0, 0),
),
],
),
),

View file

@ -28,7 +28,7 @@ class LikeButton extends StatelessWidget {
size: iconSize,
color: Colors.white24,
),
onPressed: null,
onPressed: () => musicDataStore.incrementLikeCount(song),
);
} else {
return IconButton(
@ -50,7 +50,12 @@ class LikeButton extends StatelessWidget {
],
alignment: AlignmentDirectional.center,
),
onPressed: null,
onPressed: () {
if (song.likeCount < 5) {
return musicDataStore.incrementLikeCount(song);
}
return musicDataStore.resetLikeCount(song);
},
);
}
},

View file

@ -20,22 +20,22 @@ class MyAudioHandler extends BaseAudioHandler {
_handleSetQueue(event);
});
_audioPlayer.currentSongStream.listen((songModel) {
mediaItem.add(songModel.toMediaItem());
});
_audioPlayer.currentSongStream.listen((songModel) => mediaItem.add(songModel.toMediaItem()));
_audioPlayer.playbackEventStream.listen((event) {
_handlePlaybackEvent(event);
});
_audioPlayer.playbackEventStream.listen((event) => _handlePlaybackEvent(event));
_audioPlayer.shuffleModeStream.listen((shuffleMode) {
_audioPlayer.shuffleModeStream.skip(1).listen((shuffleMode) {
_playerStateDataSource.setShuffleMode(shuffleMode);
});
_audioPlayer.loopModeStream.listen((event) {
_audioPlayer.loopModeStream.skip(1).listen((event) {
_playerStateDataSource.setLoopMode(event);
});
_audioPlayer.positionStream.listen((event) {
_handlePosition(event, _audioPlayer.currentSongStream.value);
});
_initAudioPlayer();
}
@ -63,6 +63,8 @@ class MyAudioHandler extends BaseAudioHandler {
static final _log = Logger('AudioHandler');
bool _countSongPlayback = true;
@override
Future<void> stop() async {
await _audioPlayer.stop();
@ -82,7 +84,11 @@ class MyAudioHandler extends BaseAudioHandler {
@override
Future<void> skipToNext() async {
_audioPlayer.seekToNext();
_audioPlayer.seekToNext().then((value) {
if (value) {
_musicDataSource.incrementSkipCount(_audioPlayer.currentSongStream.value);
}
});
}
@override
@ -187,4 +193,18 @@ class MyAudioHandler extends BaseAudioHandler {
}
}
}
void _handlePosition(Duration position, SongModel song) {
if (song == null || position == null)
return;
final int pos = position.inMilliseconds;
if (pos < song.duration * 0.05) {
_countSongPlayback = true;
} else if (pos > song.duration * 0.95 && _countSongPlayback) {
_countSongPlayback = false;
_musicDataSource.incrementPlayCount(song);
}
}
}

View file

@ -19,7 +19,7 @@ abstract class AudioPlayer {
Future<void> play();
Future<void> pause();
Future<void> stop();
Future<void> seekToNext();
Future<bool> seekToNext();
Future<void> seekToPrevious();
Future<void> dispose();

View file

@ -17,7 +17,7 @@ class AudioPlayerImpl implements AudioPlayer {
_audioPlayer.currentIndexStream.listen((event) {
_log.info('currentIndex: $event');
_currentIndexSubject.add(event);
if (_queueSubject.value != null) {
if (_queueSubject.value != null && event != null) {
_currentSongSubject.add(_queueSubject.value[event].song);
}
});
@ -151,13 +151,19 @@ class AudioPlayerImpl implements AudioPlayer {
}
@override
Future<void> seekToNext() async {
Future<bool> seekToNext() async {
final result = _audioPlayer.hasNext;
await _audioPlayer.seekToNext();
return result;
}
@override
Future<void> seekToPrevious() async {
await _audioPlayer.seekToPrevious();
if (_audioPlayer.position > const Duration(seconds: 3) || !_audioPlayer.hasPrevious) {
await _audioPlayer.seek(const Duration(seconds: 0));
} else {
await _audioPlayer.seekToPrevious();
}
}
@override
@ -180,10 +186,9 @@ class AudioPlayerImpl implements AudioPlayer {
@override
Future<void> moveQueueItem(int oldIndex, int newIndex) async {
final QueueItemModel queueItem = _queue.removeAt(oldIndex);
final index = newIndex < oldIndex ? newIndex : newIndex - 1;
_queue.insert(index, queueItem);
_queue.insert(newIndex, queueItem);
_queueSubject.add(_queue);
await _audioSource.move(oldIndex, index);
await _audioSource.move(oldIndex, newIndex);
}
@override
@ -195,6 +200,7 @@ class AudioPlayerImpl implements AudioPlayer {
@override
Future<void> setShuffleMode(ShuffleMode shuffleMode, bool updateQueue) async {
_log.info('setShuffleMode: $shuffleMode');
if (shuffleMode == null) return;
_shuffleModeSubject.add(shuffleMode);

View file

@ -28,8 +28,8 @@ class MusicDataDao extends DatabaseAccessor<MoorDatabase>
@override
Stream<List<SongModel>> get songStream {
return select(songs).watch().map((moorSongList) =>
moorSongList.map((moorSong) => SongModel.fromMoor(moorSong)).toList());
return select(songs).watch().map(
(moorSongList) => moorSongList.map((moorSong) => SongModel.fromMoor(moorSong)).toList());
}
@override
@ -46,8 +46,8 @@ class MusicDataDao extends DatabaseAccessor<MoorDatabase>
@override
Future<List<SongModel>> getSongs() {
return select(songs).get().then((moorSongList) =>
moorSongList.map((moorSong) => SongModel.fromMoor(moorSong)).toList());
return select(songs).get().then(
(moorSongList) => moorSongList.map((moorSong) => SongModel.fromMoor(moorSong)).toList());
}
@override
@ -170,4 +170,58 @@ class MusicDataDao extends DatabaseAccessor<MoorDatabase>
.write(const SongsCompanion(next: Value(null)));
}
}
@override
Future<void> decrementLikeCount(SongModel song) async {
final songEntry = await (select(songs)..where((tbl) => tbl.path.equals(song.path))).getSingle();
if (song.likeCount > 0) {
await (update(songs)..where((tbl) => tbl.path.equals(song.path)))
.write(SongsCompanion(likeCount: Value(songEntry.likeCount - 1)));
}
}
@override
Future<void> incrementLikeCount(SongModel song) async {
final songEntry = await (select(songs)..where((tbl) => tbl.path.equals(song.path))).getSingle();
if (song.likeCount < 5) {
await (update(songs)..where((tbl) => tbl.path.equals(song.path)))
.write(SongsCompanion(likeCount: Value(songEntry.likeCount + 1)));
}
}
@override
Future<void> incrementPlayCount(SongModel song) async {
final songEntry = await (select(songs)..where((tbl) => tbl.path.equals(song.path))).getSingle();
await (update(songs)..where((tbl) => tbl.path.equals(song.path)))
.write(SongsCompanion(playCount: Value(songEntry.playCount + 1)));
}
@override
Future<void> incrementSkipCount(SongModel song) async {
final songEntry = await (select(songs)..where((tbl) => tbl.path.equals(song.path))).getSingle();
await (update(songs)..where((tbl) => tbl.path.equals(song.path)))
.write(SongsCompanion(skipCount: Value(songEntry.skipCount + 1)));
}
@override
Future<void> resetLikeCount(SongModel song) async {
await (update(songs)..where((tbl) => tbl.path.equals(song.path)))
.write(const SongsCompanion(likeCount: Value(0)));
}
@override
Future<void> resetPlayCount(SongModel song) async {
await (update(songs)..where((tbl) => tbl.path.equals(song.path)))
.write(const SongsCompanion(playCount: Value(0)));
}
@override
Future<void> resetSkipCount(SongModel song) async {
await (update(songs)..where((tbl) => tbl.path.equals(song.path)))
.write(const SongsCompanion(skipCount: Value(0)));
}
}

View file

@ -86,11 +86,13 @@ class PlayerStateDao extends DatabaseAccessor<MoorDatabase>
Future<void> setLoopMode(LoopMode loopMode) async {
final currentState = await select(persistentLoopMode).getSingle();
if (currentState != null) {
update(persistentLoopMode)
.write(PersistentLoopModeCompanion(loopMode: Value(loopMode.toInt())));
update(persistentLoopMode).write(PersistentLoopModeCompanion(
loopMode: Value(loopMode.toInt()),
));
} else {
into(persistentLoopMode)
.insert(PersistentLoopModeCompanion(loopMode: Value(loopMode.toInt())));
into(persistentLoopMode).insert(PersistentLoopModeCompanion(
loopMode: Value(loopMode.toInt()),
));
}
}

View file

@ -17,9 +17,16 @@ abstract class MusicDataSource {
Future<void> insertSong(SongModel songModel);
Future<void> insertSongs(List<SongModel> songModels);
Future<void> deleteAllSongs();
Future<void> setSongBlocked(SongModel song, bool blocked);
Future<void> toggleNextSongLink(SongModel song);
Future<void> deleteAllSongs();
Future<void> incrementSkipCount(SongModel song);
Future<void> resetSkipCount(SongModel song);
Future<void> incrementLikeCount(SongModel song);
Future<void> decrementLikeCount(SongModel song);
Future<void> resetLikeCount(SongModel song);
Future<void> incrementPlayCount(SongModel song);
Future<void> resetPlayCount(SongModel song);
/// Insert album into the database. Return the ID of the inserted album.
Future<int> insertAlbum(AlbumModel albumModel);

View file

@ -0,0 +1,43 @@
import '../../domain/entities/song.dart';
import '../../domain/repositories/music_data_modifier_repository.dart';
import '../datasources/music_data_source_contract.dart';
import '../models/song_model.dart';
class MusicDataModifierRepositoryImpl implements MusicDataModifierRepository {
MusicDataModifierRepositoryImpl(this._musicDataSource);
final MusicDataSource _musicDataSource;
@override
Future<void> setSongBlocked(Song song, bool blocked) =>
_musicDataSource.setSongBlocked(song as SongModel, blocked);
@override
Future<void> toggleNextSongLink(Song song) =>
_musicDataSource.toggleNextSongLink(song as SongModel);
@override
Future<void> decrementLikeCount(Song song) =>
_musicDataSource.decrementLikeCount(song as SongModel);
@override
Future<void> incrementLikeCount(Song song) =>
_musicDataSource.incrementLikeCount(song as SongModel);
@override
Future<void> incrementPlayCount(Song song) =>
_musicDataSource.incrementPlayCount(song as SongModel);
@override
Future<void> incrementSkipCount(Song song) =>
_musicDataSource.incrementSkipCount(song as SongModel);
@override
Future<void> resetLikeCount(Song song) => _musicDataSource.resetLikeCount(song as SongModel);
@override
Future<void> resetPlayCount(Song song) => _musicDataSource.resetPlayCount(song as SongModel);
@override
Future<void> resetSkipCount(Song song) => _musicDataSource.resetSkipCount(song as SongModel);
}

View file

@ -50,11 +50,6 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
_log.info('updateDatabase finished');
}
@override
Future<void> setSongBlocked(Song song, bool blocked) async {
await _musicDataSource.setSongBlocked(song as SongModel, blocked);
}
Future<void> updateArtists(List<ArtistModel> artists) async {
await _musicDataSource.deleteAllArtists();
for (final ArtistModel artist in artists) {
@ -107,11 +102,6 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
await _musicDataSource.insertSongs(songsToInsert);
}
@override
Future<void> toggleNextSongLink(Song song) async {
_musicDataSource.toggleNextSongLink(song as SongModel);
}
@override
Stream<List<Album>> getArtistAlbumStream(Artist artist) {
return _musicDataSource.getArtistAlbumStream(artist as ArtistModel);