Merge pull request #35 from moritz-weber/34-add-like-button-in-media-notification
Notification changes
BIN
src/android/app/src/main/res/drawable-hdpi/favorite_0.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
src/android/app/src/main/res/drawable-hdpi/favorite_1.png
Normal file
After Width: | Height: | Size: 943 B |
BIN
src/android/app/src/main/res/drawable-hdpi/favorite_2.png
Normal file
After Width: | Height: | Size: 891 B |
BIN
src/android/app/src/main/res/drawable-hdpi/favorite_3.png
Normal file
After Width: | Height: | Size: 748 B |
Before Width: | Height: | Size: 140 B |
Before Width: | Height: | Size: 272 B |
Before Width: | Height: | Size: 102 B |
BIN
src/android/app/src/main/res/drawable-hdpi/pause.png
Normal file
After Width: | Height: | Size: 603 B |
BIN
src/android/app/src/main/res/drawable-hdpi/play.png
Normal file
After Width: | Height: | Size: 672 B |
BIN
src/android/app/src/main/res/drawable-hdpi/skip_next.png
Normal file
After Width: | Height: | Size: 614 B |
BIN
src/android/app/src/main/res/drawable-hdpi/skip_prev.png
Normal file
After Width: | Height: | Size: 687 B |
BIN
src/android/app/src/main/res/drawable-mdpi/favorite_0.png
Normal file
After Width: | Height: | Size: 594 B |
BIN
src/android/app/src/main/res/drawable-mdpi/favorite_1.png
Normal file
After Width: | Height: | Size: 539 B |
BIN
src/android/app/src/main/res/drawable-mdpi/favorite_2.png
Normal file
After Width: | Height: | Size: 505 B |
BIN
src/android/app/src/main/res/drawable-mdpi/favorite_3.png
Normal file
After Width: | Height: | Size: 457 B |
Before Width: | Height: | Size: 108 B |
Before Width: | Height: | Size: 159 B |
Before Width: | Height: | Size: 92 B |
BIN
src/android/app/src/main/res/drawable-mdpi/pause.png
Normal file
After Width: | Height: | Size: 372 B |
BIN
src/android/app/src/main/res/drawable-mdpi/play.png
Normal file
After Width: | Height: | Size: 428 B |
BIN
src/android/app/src/main/res/drawable-mdpi/skip_next.png
Normal file
After Width: | Height: | Size: 367 B |
BIN
src/android/app/src/main/res/drawable-mdpi/skip_prev.png
Normal file
After Width: | Height: | Size: 386 B |
BIN
src/android/app/src/main/res/drawable-xhdpi/favorite_0.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
src/android/app/src/main/res/drawable-xhdpi/favorite_1.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/android/app/src/main/res/drawable-xhdpi/favorite_2.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/android/app/src/main/res/drawable-xhdpi/favorite_3.png
Normal file
After Width: | Height: | Size: 926 B |
Before Width: | Height: | Size: 162 B |
Before Width: | Height: | Size: 288 B |
Before Width: | Height: | Size: 114 B |
BIN
src/android/app/src/main/res/drawable-xhdpi/pause.png
Normal file
After Width: | Height: | Size: 716 B |
BIN
src/android/app/src/main/res/drawable-xhdpi/play.png
Normal file
After Width: | Height: | Size: 751 B |
BIN
src/android/app/src/main/res/drawable-xhdpi/skip_next.png
Normal file
After Width: | Height: | Size: 670 B |
BIN
src/android/app/src/main/res/drawable-xhdpi/skip_prev.png
Normal file
After Width: | Height: | Size: 759 B |
BIN
src/android/app/src/main/res/drawable-xxhdpi/favorite_0.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
src/android/app/src/main/res/drawable-xxhdpi/favorite_1.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
src/android/app/src/main/res/drawable-xxhdpi/favorite_2.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
src/android/app/src/main/res/drawable-xxhdpi/favorite_3.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 202 B |
Before Width: | Height: | Size: 547 B |
Before Width: | Height: | Size: 196 B |
BIN
src/android/app/src/main/res/drawable-xxhdpi/pause.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/android/app/src/main/res/drawable-xxhdpi/play.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/android/app/src/main/res/drawable-xxhdpi/skip_next.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/android/app/src/main/res/drawable-xxhdpi/skip_prev.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/android/app/src/main/res/drawable-xxxhdpi/favorite_0.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
src/android/app/src/main/res/drawable-xxxhdpi/favorite_1.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
src/android/app/src/main/res/drawable-xxxhdpi/favorite_2.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
src/android/app/src/main/res/drawable-xxxhdpi/favorite_3.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 244 B |
Before Width: | Height: | Size: 488 B |
Before Width: | Height: | Size: 244 B |
BIN
src/android/app/src/main/res/drawable-xxxhdpi/pause.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
src/android/app/src/main/res/drawable-xxxhdpi/play.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
src/android/app/src/main/res/drawable-xxxhdpi/skip_next.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
src/android/app/src/main/res/drawable-xxxhdpi/skip_prev.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
|
@ -1,4 +1,5 @@
|
||||||
import '../repositories/audio_player_repository.dart';
|
import '../repositories/audio_player_repository.dart';
|
||||||
|
import '../repositories/music_data_repository.dart';
|
||||||
import '../repositories/platform_integration_repository.dart';
|
import '../repositories/platform_integration_repository.dart';
|
||||||
import '../usecases/seek_to_next.dart';
|
import '../usecases/seek_to_next.dart';
|
||||||
|
|
||||||
|
@ -7,17 +8,19 @@ class PlatformIntegrationActor {
|
||||||
this._platformIntegrationInfoRepository,
|
this._platformIntegrationInfoRepository,
|
||||||
this._seekToNext,
|
this._seekToNext,
|
||||||
this._audioPlayerRepository,
|
this._audioPlayerRepository,
|
||||||
|
this._musicDataRepository,
|
||||||
) {
|
) {
|
||||||
_platformIntegrationInfoRepository.eventStream
|
_platformIntegrationInfoRepository.eventStream
|
||||||
.listen((event) => _handlePlatformIntegrationEvent(event));
|
.listen((event) => _handlePlatformIntegrationEvent(event));
|
||||||
}
|
}
|
||||||
|
|
||||||
final AudioPlayerRepository _audioPlayerRepository;
|
final AudioPlayerRepository _audioPlayerRepository;
|
||||||
|
final MusicDataRepository _musicDataRepository;
|
||||||
final PlatformIntegrationInfoRepository _platformIntegrationInfoRepository;
|
final PlatformIntegrationInfoRepository _platformIntegrationInfoRepository;
|
||||||
|
|
||||||
final SeekToNext _seekToNext;
|
final SeekToNext _seekToNext;
|
||||||
|
|
||||||
void _handlePlatformIntegrationEvent(PlatformIntegrationEvent event) {
|
Future<void> _handlePlatformIntegrationEvent(PlatformIntegrationEvent event) async {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case PlatformIntegrationEventType.play:
|
case PlatformIntegrationEventType.play:
|
||||||
_audioPlayerRepository.play();
|
_audioPlayerRepository.play();
|
||||||
|
@ -36,6 +39,13 @@ class PlatformIntegrationActor {
|
||||||
case PlatformIntegrationEventType.seek:
|
case PlatformIntegrationEventType.seek:
|
||||||
_seekToPosition(event.payload!['position'] as Duration);
|
_seekToPosition(event.payload!['position'] as Duration);
|
||||||
break;
|
break;
|
||||||
|
case PlatformIntegrationEventType.like:
|
||||||
|
final path = event.payload?['path'];
|
||||||
|
if (path != null) {
|
||||||
|
final song = await _musicDataRepository.getSongByPath(path as String);
|
||||||
|
_musicDataRepository.incrementLikeCount(song);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,7 @@ abstract class MusicDataRepository extends MusicDataInfoRepository {
|
||||||
Future<Song> resetSkipCount(Song song);
|
Future<Song> resetSkipCount(Song song);
|
||||||
|
|
||||||
Future<void> setLikeCount(List<Song> songs, int count);
|
Future<void> setLikeCount(List<Song> songs, int count);
|
||||||
|
Future<void> incrementLikeCount(Song song);
|
||||||
|
|
||||||
Future<Song> incrementPlayCount(Song song);
|
Future<Song> incrementPlayCount(Song song);
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,6 @@ import '../entities/event.dart';
|
||||||
import '../entities/playback_event.dart';
|
import '../entities/playback_event.dart';
|
||||||
import '../entities/song.dart';
|
import '../entities/song.dart';
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
- position
|
|
||||||
- controls (playbackState)
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
abstract class PlatformIntegrationInfoRepository {
|
abstract class PlatformIntegrationInfoRepository {
|
||||||
Stream<PlatformIntegrationEvent> get eventStream;
|
Stream<PlatformIntegrationEvent> get eventStream;
|
||||||
|
@ -16,7 +10,6 @@ abstract class PlatformIntegrationInfoRepository {
|
||||||
abstract class PlatformIntegrationRepository extends PlatformIntegrationInfoRepository {
|
abstract class PlatformIntegrationRepository extends PlatformIntegrationInfoRepository {
|
||||||
void handlePlaybackEvent(PlaybackEvent playbackEvent);
|
void handlePlaybackEvent(PlaybackEvent playbackEvent);
|
||||||
void setCurrentSong(Song? song);
|
void setCurrentSong(Song? song);
|
||||||
// void setQueue(List<Song> queue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlatformIntegrationEvent extends Event {
|
class PlatformIntegrationEvent extends Event {
|
||||||
|
@ -32,4 +25,5 @@ enum PlatformIntegrationEventType {
|
||||||
skipNext,
|
skipNext,
|
||||||
skipPrevious,
|
skipPrevious,
|
||||||
seek,
|
seek,
|
||||||
|
like,
|
||||||
}
|
}
|
||||||
|
|
|
@ -349,6 +349,7 @@ Future<void> setupGetIt() async {
|
||||||
getIt(),
|
getIt(),
|
||||||
getIt(),
|
getIt(),
|
||||||
getIt(),
|
getIt(),
|
||||||
|
getIt(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,50 @@ import '../models/playback_event_model.dart';
|
||||||
import '../models/song_model.dart';
|
import '../models/song_model.dart';
|
||||||
import 'platform_integration_data_source.dart';
|
import 'platform_integration_data_source.dart';
|
||||||
|
|
||||||
|
const favs = [
|
||||||
|
MediaControl(
|
||||||
|
androidIcon: 'drawable/favorite_0',
|
||||||
|
label: 'Like',
|
||||||
|
action: MediaAction.rewind,
|
||||||
|
),
|
||||||
|
MediaControl(
|
||||||
|
androidIcon: 'drawable/favorite_1',
|
||||||
|
label: 'Like',
|
||||||
|
action: MediaAction.rewind,
|
||||||
|
),
|
||||||
|
MediaControl(
|
||||||
|
androidIcon: 'drawable/favorite_2',
|
||||||
|
label: 'Like',
|
||||||
|
action: MediaAction.rewind,
|
||||||
|
),
|
||||||
|
MediaControl(
|
||||||
|
androidIcon: 'drawable/favorite_3',
|
||||||
|
label: 'Like',
|
||||||
|
action: MediaAction.rewind,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
const playCtrl = MediaControl(
|
||||||
|
androidIcon: 'drawable/play',
|
||||||
|
label: 'Play',
|
||||||
|
action: MediaAction.play,
|
||||||
|
);
|
||||||
|
const pauseCtrl = MediaControl(
|
||||||
|
androidIcon: 'drawable/pause',
|
||||||
|
label: 'Pause',
|
||||||
|
action: MediaAction.pause,
|
||||||
|
);
|
||||||
|
const nextCtrl = MediaControl(
|
||||||
|
androidIcon: 'drawable/skip_next',
|
||||||
|
label: 'Next',
|
||||||
|
action: MediaAction.skipToNext,
|
||||||
|
);
|
||||||
|
const prevCtrl = MediaControl(
|
||||||
|
androidIcon: 'drawable/skip_prev',
|
||||||
|
label: 'Previous',
|
||||||
|
action: MediaAction.skipToPrevious,
|
||||||
|
);
|
||||||
|
|
||||||
class PlatformIntegrationDataSourceImpl extends BaseAudioHandler
|
class PlatformIntegrationDataSourceImpl extends BaseAudioHandler
|
||||||
implements PlatformIntegrationDataSource {
|
implements PlatformIntegrationDataSource {
|
||||||
PlatformIntegrationDataSourceImpl();
|
PlatformIntegrationDataSourceImpl();
|
||||||
|
@ -52,6 +96,13 @@ class PlatformIntegrationDataSourceImpl extends BaseAudioHandler
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> rewind() async {
|
||||||
|
_log.d('rewind -> like');
|
||||||
|
_eventSubject.add(PlatformIntegrationEvent(
|
||||||
|
type: PlatformIntegrationEventType.like, payload: {'path': mediaItem.value?.id}));
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> click([MediaButton button = MediaButton.media]) async {
|
Future<void> click([MediaButton button = MediaButton.media]) async {
|
||||||
_log.i(button.toString());
|
_log.i(button.toString());
|
||||||
|
@ -89,39 +140,45 @@ class PlatformIntegrationDataSourceImpl extends BaseAudioHandler
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> handlePlaybackEvent(PlaybackEventModel pe) async {
|
Future<void> handlePlaybackEvent(PlaybackEventModel pe) async {
|
||||||
|
final mi = mediaItem.value;
|
||||||
|
final int likeCount = mi == null ? 0 : mi.extras!['likeCount'] as int;
|
||||||
|
|
||||||
if (pe.processingState == ProcessingState.ready) {
|
if (pe.processingState == ProcessingState.ready) {
|
||||||
final timeDelta = DateTime.now().difference(pe.updateTime);
|
final timeDelta = DateTime.now().difference(pe.updateTime);
|
||||||
if (pe.playing) {
|
if (pe.playing) {
|
||||||
playbackState.add(playbackState.value.copyWith(
|
playbackState.add(playbackState.value.copyWith(
|
||||||
controls: [MediaControl.skipToPrevious, MediaControl.pause, MediaControl.skipToNext],
|
controls: [favs[likeCount], prevCtrl, pauseCtrl, nextCtrl],
|
||||||
systemActions: const {
|
systemActions: const {
|
||||||
MediaAction.seek,
|
MediaAction.seek,
|
||||||
},
|
},
|
||||||
playing: true,
|
playing: true,
|
||||||
processingState: AudioProcessingState.ready,
|
processingState: AudioProcessingState.ready,
|
||||||
updatePosition: pe.updatePosition + timeDelta,
|
updatePosition: pe.updatePosition + timeDelta,
|
||||||
|
androidCompactActionIndices: [0, 2, 3],
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
playbackState.add(playbackState.value.copyWith(
|
playbackState.add(playbackState.value.copyWith(
|
||||||
controls: [MediaControl.skipToPrevious, MediaControl.play, MediaControl.skipToNext],
|
controls: [favs[likeCount], prevCtrl, playCtrl, nextCtrl],
|
||||||
systemActions: const {
|
systemActions: const {
|
||||||
MediaAction.seek,
|
MediaAction.seek,
|
||||||
},
|
},
|
||||||
processingState: AudioProcessingState.ready,
|
processingState: AudioProcessingState.ready,
|
||||||
updatePosition: pe.updatePosition + timeDelta,
|
updatePosition: pe.updatePosition + timeDelta,
|
||||||
playing: false,
|
playing: false,
|
||||||
|
androidCompactActionIndices: [0, 2, 3],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else if (pe.processingState == ProcessingState.completed) {
|
} else if (pe.processingState == ProcessingState.completed) {
|
||||||
final timeDelta = DateTime.now().difference(pe.updateTime);
|
final timeDelta = DateTime.now().difference(pe.updateTime);
|
||||||
playbackState.add(playbackState.value.copyWith(
|
playbackState.add(playbackState.value.copyWith(
|
||||||
controls: [MediaControl.skipToPrevious, MediaControl.play, MediaControl.skipToNext],
|
controls: [favs[likeCount], prevCtrl, playCtrl, nextCtrl],
|
||||||
systemActions: const {
|
systemActions: const {
|
||||||
MediaAction.seek,
|
MediaAction.seek,
|
||||||
},
|
},
|
||||||
processingState: AudioProcessingState.ready,
|
processingState: AudioProcessingState.ready,
|
||||||
updatePosition: pe.updatePosition + timeDelta,
|
updatePosition: pe.updatePosition + timeDelta,
|
||||||
playing: false,
|
playing: false,
|
||||||
|
androidCompactActionIndices: [0, 2, 3],
|
||||||
));
|
));
|
||||||
} else if (pe.processingState == ProcessingState.none) {
|
} else if (pe.processingState == ProcessingState.none) {
|
||||||
stop();
|
stop();
|
||||||
|
@ -133,6 +190,17 @@ class PlatformIntegrationDataSourceImpl extends BaseAudioHandler
|
||||||
@override
|
@override
|
||||||
Future<void> setCurrentSong(SongModel? songModel) async {
|
Future<void> setCurrentSong(SongModel? songModel) async {
|
||||||
mediaItem.add(songModel?.toMediaItem());
|
mediaItem.add(songModel?.toMediaItem());
|
||||||
|
|
||||||
|
if (songModel != null) {
|
||||||
|
final state = playbackState.value;
|
||||||
|
final controls = state.controls.sublist(1);
|
||||||
|
final timeDelta = state.playing ? DateTime.now().difference(state.updateTime) : Duration.zero;
|
||||||
|
|
||||||
|
playbackState.add(playbackState.value.copyWith(
|
||||||
|
controls: [favs[songModel.likeCount]] + controls,
|
||||||
|
updatePosition: state.updatePosition + timeDelta,
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -175,6 +175,12 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> incrementLikeCount(Song song) async {
|
||||||
|
final count = song.likeCount < MAX_LIKE_COUNT ? song.likeCount + 1 : 0;
|
||||||
|
await setLikeCount([song], count);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Song> resetSkipCount(Song song) async {
|
Future<Song> resetSkipCount(Song song) async {
|
||||||
final newSong = (song as SongModel).copyWith(skipCount: 0);
|
final newSong = (song as SongModel).copyWith(skipCount: 0);
|
||||||
|
|