initial work for artist of the day and artist id
This commit is contained in:
parent
d1f386396a
commit
8e4ef48180
13 changed files with 399 additions and 111 deletions
|
@ -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';
|
|
@ -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});
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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}
|
||||
''';
|
||||
}
|
||||
}
|
||||
|
|
113
lib/presentation/widgets/highlight_artist.dart
Normal file
113
lib/presentation/widgets/highlight_artist.dart
Normal 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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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});
|
||||
|
|
|
@ -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}}';
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue