initial work for artist of the day and artist id

This commit is contained in:
Moritz Weber 2022-09-16 19:08:12 +02:00
parent d1f386396a
commit 8e4ef48180
13 changed files with 399 additions and 111 deletions

View file

@ -7,3 +7,6 @@ const String PERSISTENT_PLAYABLE = 'PERSISTENT_PLAYABLE';
const String SETTING_EXCLUDE_SKIPPED_SONGS = 'SETTING_EXCLUDE_SKIPPED_SONGS_ENABLED';
const String SETTING_SKIP_THRESHOLD = 'SETTING_SKIP_THRESHOLD';
const String ALBUM_OF_DAY = 'ALBUM_OF_DAY';
const String ARTIST_OF_DAY = 'ARTIST_OF_DAY';

View file

@ -1,3 +1,5 @@
import 'package:rxdart/rxdart.dart';
import '../entities/album.dart';
import '../entities/artist.dart';
import '../entities/playlist.dart';
@ -28,11 +30,12 @@ abstract class MusicDataInfoRepository {
Stream<List<Album>> get albumStream;
Stream<List<Album>> getArtistAlbumStream(Artist artist);
Future<int?> getAlbumId(String title, String artist, int? year);
// TODO: make this a stream? or call everytime on home screen?
Future<Album?> getAlbumOfDay();
Stream<List<Artist>> get artistStream;
ValueStream<Album?> get albumOfDayStream;
ValueStream<Artist?> get artistOfDayStream;
Future<List<Artist>> searchArtists(String searchText, {int? limit});
Future<List<Album>> searchAlbums(String searchText, {int? limit});
Future<List<Song>> searchSongs(String searchText, {int? limit});

View file

@ -55,7 +55,11 @@ abstract class _MusicDataStore with Store {
bool isUpdatingDatabase = false;
@observable
late ObservableFuture<Album?> albumOfDay = _musicDataRepository.getAlbumOfDay().asObservable();
late ObservableStream<Album?> albumOfDay = _musicDataRepository.albumOfDayStream.asObservable();
@observable
late ObservableStream<Artist?> artistOfDay =
_musicDataRepository.artistOfDayStream.asObservable();
@action
Future<void> updateDatabase() async {

View file

@ -109,18 +109,34 @@ mixin _$MusicDataStore on _MusicDataStore, Store {
Atom(name: '_MusicDataStore.albumOfDay', context: context);
@override
ObservableFuture<Album?> get albumOfDay {
ObservableStream<Album?> get albumOfDay {
_$albumOfDayAtom.reportRead();
return super.albumOfDay;
}
@override
set albumOfDay(ObservableFuture<Album?> value) {
set albumOfDay(ObservableStream<Album?> value) {
_$albumOfDayAtom.reportWrite(value, super.albumOfDay, () {
super.albumOfDay = value;
});
}
late final _$artistOfDayAtom =
Atom(name: '_MusicDataStore.artistOfDay', context: context);
@override
ObservableStream<Artist?> get artistOfDay {
_$artistOfDayAtom.reportRead();
return super.artistOfDay;
}
@override
set artistOfDay(ObservableStream<Artist?> value) {
_$artistOfDayAtom.reportWrite(value, super.artistOfDay, () {
super.artistOfDay = value;
});
}
late final _$updateDatabaseAsyncAction =
AsyncAction('_MusicDataStore.updateDatabase', context: context);
@ -138,7 +154,8 @@ artistStream: ${artistStream},
playlistsStream: ${playlistsStream},
smartListsStream: ${smartListsStream},
isUpdatingDatabase: ${isUpdatingDatabase},
albumOfDay: ${albumOfDay}
albumOfDay: ${albumOfDay},
artistOfDay: ${artistOfDay}
''';
}
}

View file

@ -0,0 +1,113 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:get_it/get_it.dart';
import '../../domain/entities/artist.dart';
import '../pages/artist_details_page.dart';
import '../state/audio_store.dart';
import '../state/music_data_store.dart';
import '../state/navigation_store.dart';
import '../theming.dart';
class HighlightArtist extends StatelessWidget {
const HighlightArtist({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final AudioStore audioStore = GetIt.I<AudioStore>();
final MusicDataStore musicDataStore = GetIt.I<MusicDataStore>();
final NavigationStore navStore = GetIt.I<NavigationStore>();
return Observer(
builder: (context) {
final Artist? artist = musicDataStore.artistOfDay.value;
if (artist == null) return Container();
return GestureDetector(
onTap: () => navStore.pushOnLibrary(
MaterialPageRoute<Widget>(
builder: (context) => ArtistDetailsPage(artist: artist),
),
),
child: Container(
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(8.0),
boxShadow: const [
BoxShadow(
color: Colors.black54,
blurRadius: 8,
offset: Offset(0, 1),
spreadRadius: -5,
),
],
),
child: Material(
color: Colors.transparent,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
width: 100.0,
child: AspectRatio(
aspectRatio: 1,
child: Container(
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(2.0),
boxShadow: const [
BoxShadow(color: Colors.black54, blurRadius: 4, offset: Offset(0, 0), spreadRadius: -1),
],
),
// child: Image(
// image: getAlbumImage(artist.albumArtPath),
// fit: BoxFit.cover,
// ),
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 8.0, top: 8.0, bottom: 8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Artist of the Day'.toUpperCase(),
style: TEXT_SMALL_HEADLINE,
),
Container(height: 6.0),
Text(
artist.name,
style: Theme.of(context).textTheme.headline4,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
),
IconButton(
icon: const Icon(
Icons.play_circle_fill_rounded,
size: 48.0,
),
iconSize: 48.0,
onPressed: () => {}, //audioStore.playArtist(artist),
splashRadius: 28.0,
),
],
),
),
),
);
},
);
}
}

View file

@ -1,6 +1,7 @@
import 'dart:io';
import 'package:audiotagger/audiotagger.dart';
import 'package:collection/collection.dart';
import 'package:flutter_fimber/flutter_fimber.dart';
import 'package:path_provider/path_provider.dart';
@ -43,14 +44,18 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
final List<AlbumModel> albums = [];
final Map<String, int> albumIdMap = {};
final Map<String, String> albumArtMap = {};
final List<ArtistModel> artists = [];
final Set<String> artistSet = {};
final Set<ArtistModel> artistSet = {};
final albumsInDb = (await _musicDataSource.albumStream.first)
..sort((a, b) => a.id.compareTo(b.id));
int newAlbumId = albumsInDb.isNotEmpty ? albumsInDb.last.id + 1 : 0;
_log.d('New albums start with id: $newAlbumId');
final artistsInDb = (await _musicDataSource.artistStream.first)
..sort((a, b) => a.id.compareTo(b.id));
int newArtistId = artistsInDb.isNotEmpty ? artistsInDb.last.id + 1 : 0;
_log.d('New artists start with id: $newArtistId');
final Directory dir = await getApplicationSupportDirectory();
for (final file in files.toSet()) {
@ -74,7 +79,8 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
albums.add(album);
albumIdMap[albumString] = album.id;
if (album.albumArtPath != null) albumArtMap[albumString] = album.albumArtPath!;
artistSet.add(album.artist);
final artist = artistsInDb.singleWhere((a) => a.name == album.artist);
artistSet.add(artist);
} else {
// we already encountered the album (at least by albumString)
// make sure the id is consistent
@ -107,7 +113,10 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
// we haven't seen an album with these properties in the files yet, but there might be an entry in the database
// in this case, we should use the corresponding ID
albumId ??= await _musicDataSource.getAlbumId(
tags.album, tags.albumArtist, int.tryParse(tags.year ?? '')) ??
tags.album,
tags.albumArtist,
int.tryParse(tags.year ?? ''),
) ??
newAlbumId++;
albumIdMap[albumString] = albumId;
@ -120,12 +129,24 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
albumArtMap[albumString] = albumArtPath;
}
final String albumArtist = tags.albumArtist ?? '';
final String songArtist = tags.artist ?? '';
final String artistName = albumArtist != '' ? albumArtist : (songArtist != '' ? songArtist : DEF_ARTIST);
final artist = artistsInDb.firstWhereOrNull((a) => a.name == artistName);
if (artist != null) {
artistSet.add(artist);
} else if (artistSet.firstWhereOrNull((a) => a.name == artistName) == null) {
// artist is also not in the set already
artistSet.add(ArtistModel(name: artistName, id: newArtistId++));
}
albums.add(
AlbumModel.fromAudiotagger(albumId: albumId, tag: tags, albumArtPath: albumArtPath),
);
final String albumArtist = tags.albumArtist ?? '';
final String artist = tags.artist ?? '';
artistSet.add(albumArtist != '' ? albumArtist : (artist != '' ? artist : DEF_ARTIST));
} else {
// an album with the same properties is already stored in the list
// use it's ID regardless of the old one stored in the songModel
@ -145,14 +166,10 @@ class LocalMusicFetcherImpl implements LocalMusicFetcher {
);
}
for (final artist in artistSet) {
artists.add(ArtistModel(name: artist));
}
return {
'SONGS': songs,
'ALBUMS': albums,
'ARTISTS': artists,
'ARTISTS': artistSet.toList(),
};
}
}

View file

@ -1,6 +1,9 @@
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:drift/drift.dart';
import '../../../constants.dart';
import '../../models/album_model.dart';
import '../../models/artist_model.dart';
import '../../models/song_model.dart';
@ -9,7 +12,8 @@ import '../music_data_source_contract.dart';
part 'music_data_dao.g.dart';
@DriftAccessor(tables: [Albums, Artists, Songs, MoorAlbumOfDay, Playlists, PlaylistEntries])
@DriftAccessor(
tables: [Albums, Artists, Songs, MoorAlbumOfDay, Playlists, PlaylistEntries, KeyValueEntries])
class MusicDataDao extends DatabaseAccessor<MoorDatabase>
with _$MusicDataDaoMixin
implements MusicDataSource {
@ -214,6 +218,39 @@ class MusicDataDao extends DatabaseAccessor<MoorDatabase>
});
}
@override
Future<ArtistOfDay?> getArtistOfDay() async {
final value = await (select(keyValueEntries)..where((tbl) => tbl.key.equals(ARTIST_OF_DAY)))
.getSingleOrNull()
.then((entry) => entry?.value);
if (value == null) {
return null;
}
final dict = jsonDecode(value);
final String name = dict['name'] as String;
final int millisecondsSinceEpoch = dict['date'] as int;
final ArtistModel? artist = await (select(artists)..where((tbl) => tbl.name.equals(name)))
.getSingleOrNull()
.then((value) => value == null ? null : ArtistModel.fromMoor(value));
if (artist == null) return null;
return ArtistOfDay(artist, DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch));
}
@override
Future<void> setArtistOfDay(ArtistOfDay artistOfDay) async {
await into(keyValueEntries).insertOnConflictUpdate(
KeyValueEntriesCompanion(
key: const Value(ARTIST_OF_DAY),
value: Value(artistOfDay.toString()),
),
);
}
@override
Future<List<AlbumModel>> searchAlbums(String searchText, {int? limit}) async {
final List<AlbumModel> result = await (select(albums)

View file

@ -13,4 +13,5 @@ mixin _$MusicDataDaoMixin on DatabaseAccessor<MoorDatabase> {
$MoorAlbumOfDayTable get moorAlbumOfDay => attachedDatabase.moorAlbumOfDay;
$PlaylistsTable get playlists => attachedDatabase.playlists;
$PlaylistEntriesTable get playlistEntries => attachedDatabase.playlistEntries;
$KeyValueEntriesTable get keyValueEntries => attachedDatabase.keyValueEntries;
}

View file

@ -22,9 +22,10 @@ const String MOOR_ISOLATE = 'MOOR_ISOLATE';
@DataClassName('MoorArtist')
class Artists extends Table {
TextColumn get name => text()();
IntColumn get id => integer()();
@override
Set<Column> get primaryKey => {name};
Set<Column> get primaryKey => {id};
}
@DataClassName('MoorAlbum')
@ -192,65 +193,76 @@ class MoorDatabase extends _$MoorDatabase {
MoorDatabase.connect(DatabaseConnection connection) : super.connect(connection);
@override
int get schemaVersion => 6;
int get schemaVersion => 7;
@override
MigrationStrategy get migration => MigrationStrategy(beforeOpen: (details) async {
if (details.wasCreated) {
await into(keyValueEntries).insert(
const KeyValueEntriesCompanion(key: Value(PERSISTENT_INDEX), value: Value('0')),
);
await into(keyValueEntries).insert(
const KeyValueEntriesCompanion(key: Value(PERSISTENT_LOOPMODE), value: Value('0')),
);
await into(keyValueEntries).insert(
const KeyValueEntriesCompanion(key: Value(PERSISTENT_SHUFFLEMODE), value: Value('0')),
);
final Map initialPlayable = {
'id': '',
'type': PlayableType.all.toString(),
};
await into(keyValueEntries).insert(
KeyValueEntriesCompanion(
key: const Value(PERSISTENT_PLAYABLE),
value: Value(jsonEncode(initialPlayable)),
),
);
}
}, onUpgrade: (Migrator m, int from, int to) async {
print('$from -> $to');
if (from < 2) {
await m.addColumn(smartLists, smartLists.blockLevel);
await m.alterTable(TableMigration(smartLists));
}
if (from < 3) {
await m.addColumn(songs, songs.lastModified);
await m.alterTable(
TableMigration(songs, columnTransformer: {
songs.lastModified: Constant(DateTime.fromMillisecondsSinceEpoch(0)),
}),
);
}
if (from < 4) {
await m.alterTable(
TableMigration(songs, columnTransformer: {
songs.previous: const Constant(false),
songs.next: const Constant(false),
}),
);
}
if (from < 5) {
await m.addColumn(smartLists, smartLists.icon);
await m.addColumn(smartLists, smartLists.gradient);
await m.alterTable(TableMigration(smartLists));
}
if (from < 6) {
await m.addColumn(playlists, playlists.shuffleMode);
await m.addColumn(playlists, playlists.icon);
await m.addColumn(playlists, playlists.gradient);
await m.alterTable(TableMigration(playlists));
}
});
MigrationStrategy get migration => MigrationStrategy(
beforeOpen: (details) async {
if (details.wasCreated) {
await into(keyValueEntries).insert(
const KeyValueEntriesCompanion(key: Value(PERSISTENT_INDEX), value: Value('0')),
);
await into(keyValueEntries).insert(
const KeyValueEntriesCompanion(key: Value(PERSISTENT_LOOPMODE), value: Value('0')),
);
await into(keyValueEntries).insert(
const KeyValueEntriesCompanion(key: Value(PERSISTENT_SHUFFLEMODE), value: Value('0')),
);
final Map initialPlayable = {
'id': '',
'type': PlayableType.all.toString(),
};
await into(keyValueEntries).insert(
KeyValueEntriesCompanion(
key: const Value(PERSISTENT_PLAYABLE),
value: Value(jsonEncode(initialPlayable)),
),
);
}
},
onUpgrade: (Migrator m, int from, int to) async {
print('$from -> $to');
if (from < 2) {
await m.addColumn(smartLists, smartLists.blockLevel);
await m.alterTable(TableMigration(smartLists));
}
if (from < 3) {
await m.addColumn(songs, songs.lastModified);
await m.alterTable(
TableMigration(songs, columnTransformer: {
songs.lastModified: Constant(DateTime.fromMillisecondsSinceEpoch(0)),
}),
);
}
if (from < 4) {
await m.alterTable(
TableMigration(songs, columnTransformer: {
songs.previous: const Constant(false),
songs.next: const Constant(false),
}),
);
}
if (from < 5) {
await m.addColumn(smartLists, smartLists.icon);
await m.addColumn(smartLists, smartLists.gradient);
await m.alterTable(TableMigration(smartLists));
}
if (from < 6) {
await m.addColumn(playlists, playlists.shuffleMode);
await m.addColumn(playlists, playlists.icon);
await m.addColumn(playlists, playlists.gradient);
await m.alterTable(TableMigration(playlists));
}
if (from < 7) {
await m.addColumn(artists, artists.id);
await m.alterTable(
TableMigration(artists, columnTransformer: {
artists.id: artists.rowId,
}),
);
}
},
);
}
LazyDatabase _openConnection() {

View file

@ -294,24 +294,29 @@ class $AlbumsTable extends Albums with TableInfo<$AlbumsTable, MoorAlbum> {
class MoorArtist extends DataClass implements Insertable<MoorArtist> {
final String name;
MoorArtist({required this.name});
final int id;
MoorArtist({required this.name, required this.id});
factory MoorArtist.fromData(Map<String, dynamic> data, {String? prefix}) {
final effectivePrefix = prefix ?? '';
return MoorArtist(
name: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}name'])!,
id: const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}id'])!,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['name'] = Variable<String>(name);
map['id'] = Variable<int>(id);
return map;
}
ArtistsCompanion toCompanion(bool nullToAbsent) {
return ArtistsCompanion(
name: Value(name),
id: Value(id),
);
}
@ -320,6 +325,7 @@ class MoorArtist extends DataClass implements Insertable<MoorArtist> {
serializer ??= driftRuntimeOptions.defaultSerializer;
return MoorArtist(
name: serializer.fromJson<String>(json['name']),
id: serializer.fromJson<int>(json['id']),
);
}
@override
@ -327,47 +333,56 @@ class MoorArtist extends DataClass implements Insertable<MoorArtist> {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'name': serializer.toJson<String>(name),
'id': serializer.toJson<int>(id),
};
}
MoorArtist copyWith({String? name}) => MoorArtist(
MoorArtist copyWith({String? name, int? id}) => MoorArtist(
name: name ?? this.name,
id: id ?? this.id,
);
@override
String toString() {
return (StringBuffer('MoorArtist(')
..write('name: $name')
..write('name: $name, ')
..write('id: $id')
..write(')'))
.toString();
}
@override
int get hashCode => name.hashCode;
int get hashCode => Object.hash(name, id);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is MoorArtist && other.name == this.name);
(other is MoorArtist && other.name == this.name && other.id == this.id);
}
class ArtistsCompanion extends UpdateCompanion<MoorArtist> {
final Value<String> name;
final Value<int> id;
const ArtistsCompanion({
this.name = const Value.absent(),
this.id = const Value.absent(),
});
ArtistsCompanion.insert({
required String name,
this.id = const Value.absent(),
}) : name = Value(name);
static Insertable<MoorArtist> custom({
Expression<String>? name,
Expression<int>? id,
}) {
return RawValuesInsertable({
if (name != null) 'name': name,
if (id != null) 'id': id,
});
}
ArtistsCompanion copyWith({Value<String>? name}) {
ArtistsCompanion copyWith({Value<String>? name, Value<int>? id}) {
return ArtistsCompanion(
name: name ?? this.name,
id: id ?? this.id,
);
}
@ -377,13 +392,17 @@ class ArtistsCompanion extends UpdateCompanion<MoorArtist> {
if (name.present) {
map['name'] = Variable<String>(name.value);
}
if (id.present) {
map['id'] = Variable<int>(id.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('ArtistsCompanion(')
..write('name: $name')
..write('name: $name, ')
..write('id: $id')
..write(')'))
.toString();
}
@ -399,8 +418,13 @@ class $ArtistsTable extends Artists with TableInfo<$ArtistsTable, MoorArtist> {
late final GeneratedColumn<String?> name = GeneratedColumn<String?>(
'name', aliasedName, false,
type: const StringType(), requiredDuringInsert: true);
final VerificationMeta _idMeta = const VerificationMeta('id');
@override
List<GeneratedColumn> get $columns => [name];
late final GeneratedColumn<int?> id = GeneratedColumn<int?>(
'id', aliasedName, false,
type: const IntType(), requiredDuringInsert: false);
@override
List<GeneratedColumn> get $columns => [name, id];
@override
String get aliasedName => _alias ?? 'artists';
@override
@ -416,11 +440,14 @@ class $ArtistsTable extends Artists with TableInfo<$ArtistsTable, MoorArtist> {
} else if (isInserting) {
context.missing(_nameMeta);
}
if (data.containsKey('id')) {
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
}
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => {name};
Set<GeneratedColumn> get $primaryKey => {id};
@override
MoorArtist map(Map<String, dynamic> data, {String? tablePrefix}) {
return MoorArtist.fromData(data,

View file

@ -25,9 +25,10 @@ abstract class MusicDataSource {
Future<void> insertArtists(List<ArtistModel> artistModels);
Future<void> deleteAllArtists();
// TODO: is this the right place? maybe persistent state?
Future<void> setAlbumOfDay(AlbumOfDay albumOfDay);
Future<AlbumOfDay?> getAlbumOfDay();
Future<void> setArtistOfDay(ArtistOfDay artistOfDay);
Future<ArtistOfDay?> getArtistOfDay();
Future<List<ArtistModel>> searchArtists(String searchText, {int? limit});
Future<List<AlbumModel>> searchAlbums(String searchText, {int? limit});

View file

@ -6,14 +6,18 @@ import '../datasources/moor_database.dart';
class ArtistModel extends Artist {
const ArtistModel({
required String name,
required this.id,
}) : super(
name: name,
);
factory ArtistModel.fromMoor(MoorArtist moorArtist) => ArtistModel(
name: moorArtist.name,
id: moorArtist.id,
);
final int id;
@override
String toString() {
return name;
@ -23,3 +27,15 @@ class ArtistModel extends Artist {
name: Value(name),
);
}
class ArtistOfDay {
ArtistOfDay(this.artistModel, this.date);
final ArtistModel artistModel;
final DateTime date;
@override
String toString() {
return '{"name": "${artistModel.name}", "date": ${date.millisecondsSinceEpoch}}';
}
}

View file

@ -28,6 +28,22 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
this._playlistDataSource,
) {
_musicDataSource.songStream.listen((event) => _songSubject.add(event));
_getAlbumOfDay().then((value) => _albumOfDaySubject.add(value));
_getArtistOfDay().then((value) => _artistOfDaySubject.add(value));
_minuteStream.listen(
(event) {
_getAlbumOfDay().then((value) {
if (value != null) {
_albumOfDaySubject.add(value);
}
});
_getArtistOfDay().then((value) {
if (value != null) {
_artistOfDaySubject.add(value);
}
});
},
);
}
final LocalMusicFetcher _localMusicFetcher;
@ -36,12 +52,21 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
final BehaviorSubject<Map<String, Song>> _songUpdateSubject = BehaviorSubject();
final BehaviorSubject<List<Song>> _songSubject = BehaviorSubject();
final Stream _minuteStream = Stream.periodic(const Duration(minutes: 1));
final BehaviorSubject<Album?> _albumOfDaySubject = BehaviorSubject();
final BehaviorSubject<Artist?> _artistOfDaySubject = BehaviorSubject();
static final _log = FimberLog('MusicDataRepositoryImpl');
@override
Stream<Map<String, Song>> get songUpdateStream => _songUpdateSubject.stream;
@override
ValueStream<Album?> get albumOfDayStream => _albumOfDaySubject.stream;
@override
ValueStream<Artist?> get artistOfDayStream => _artistOfDaySubject.stream;
@override
Future<Song> getSongByPath(String path) async {
// this method is only called from upper layers
@ -245,30 +270,6 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
});
}
@override
Future<Album?> getAlbumOfDay() async {
final storedAlbum = await _musicDataSource.getAlbumOfDay();
if (storedAlbum == null || !_isAlbumValid(storedAlbum)) {
final albums = await _musicDataSource.albumStream.first;
if (albums.isNotEmpty) {
final rng = Random();
final index = rng.nextInt(albums.length);
_musicDataSource.setAlbumOfDay(AlbumOfDay(albums[index], _day(DateTime.now())));
return albums[index];
}
} else {
return storedAlbum.albumModel;
}
}
bool _isAlbumValid(AlbumOfDay albumOfDay) {
return _day(DateTime.now()).difference(_day(albumOfDay.date)).inDays < 1;
}
DateTime _day(DateTime dateTime) {
return DateTime(dateTime.year, dateTime.month, dateTime.day);
}
@override
Future<List<Album>> searchAlbums(String searchText, {int? limit}) async {
if (searchText == '') return [];
@ -455,4 +456,40 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
Future<int?> getAlbumId(String title, String artist, int? year) async {
return _musicDataSource.getAlbumId(title, artist, year);
}
Future<Album?> _getAlbumOfDay() async {
final storedAlbum = await _musicDataSource.getAlbumOfDay();
if (storedAlbum == null || !_isSameDay(storedAlbum.date)) {
final albums = await _musicDataSource.albumStream.first;
if (albums.isNotEmpty) {
final rng = Random();
final index = rng.nextInt(albums.length);
_musicDataSource.setAlbumOfDay(AlbumOfDay(albums[index], _day(DateTime.now())));
return albums[index];
}
}
return storedAlbum?.albumModel;
}
Future<Artist?> _getArtistOfDay() async {
final storedArtist = await _musicDataSource.getArtistOfDay();
if (storedArtist == null || !_isSameDay(storedArtist.date)) {
final artists = await _musicDataSource.artistStream.first;
if (artists.isNotEmpty) {
final rng = Random();
final index = rng.nextInt(artists.length);
_musicDataSource.setArtistOfDay(ArtistOfDay(artists[index], _day(DateTime.now())));
return artists[index];
}
}
return storedArtist?.artistModel;
}
bool _isSameDay(DateTime date) {
return _day(DateTime.now()).difference(_day(date)).inDays < 1;
}
DateTime _day(DateTime dateTime) {
return DateTime(dateTime.year, dateTime.month, dateTime.day);
}
}