finished smart lists (for now) #14

This commit is contained in:
Moritz Weber 2021-09-30 21:59:05 +02:00
parent cff92f8226
commit 8a9c1dbdff
13 changed files with 267 additions and 36 deletions

View file

@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:get_it/get_it.dart';
import 'package:mucke/presentation/pages/library_folders_page.dart';
import 'library_folders_page.dart';
import '../state/music_data_store.dart';
import '../state/settings_store.dart';

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:get_it/get_it.dart';
import 'package:mucke/presentation/pages/smart_lists_artists_page.dart';
import 'smart_lists_artists_page.dart';
import 'package:reorderables/reorderables.dart';
import '../../domain/entities/smart_list.dart';
@ -169,6 +169,78 @@ class _SmartListFormPageState extends State<SmartListFormPage> {
onChanged: (value) {
store.maxPlayCount = value;
},
decoration: InputDecoration(
errorText: store.error.maxPlayCount,
errorStyle: const TextStyle(height: 0, fontSize: 0),
),
),
),
],
);
},
),
),
const SizedBox(height: 24.0),
Padding(
padding: const EdgeInsets.symmetric(horizontal: HORIZONTAL_PADDING),
child: Observer(
builder: (_) {
return Row(
children: [
Switch(
value: store.minYearEnabled,
onChanged: (bool value) => store.minYearEnabled = value,
),
const Text('Minimum year'),
const Spacer(),
SizedBox(
width: 36.0,
child: TextFormField(
enabled: store.minYearEnabled,
keyboardType: TextInputType.number,
initialValue: store.minYear,
onChanged: (value) {
store.minYear = value;
},
textAlign: TextAlign.center,
decoration: InputDecoration(
errorText: store.error.minYear,
errorStyle: const TextStyle(height: 0, fontSize: 0),
),
),
),
],
);
},
),
),
const SizedBox(height: 24.0),
Padding(
padding: const EdgeInsets.symmetric(horizontal: HORIZONTAL_PADDING),
child: Observer(
builder: (_) {
return Row(
children: [
Switch(
value: store.maxYearEnabled,
onChanged: (bool value) => store.maxYearEnabled = value,
),
const Text('Maximum year'),
const Spacer(),
SizedBox(
width: 36.0,
child: TextFormField(
enabled: store.maxYearEnabled,
keyboardType: TextInputType.number,
initialValue: store.maxYear.toString(),
textAlign: TextAlign.center,
onChanged: (value) {
store.maxYear = value;
},
decoration: InputDecoration(
errorText: store.error.maxYear,
errorStyle: const TextStyle(height: 0, fontSize: 0),
),
),
),
],

View file

@ -42,6 +42,16 @@ abstract class _SmartListStore with Store {
@observable
late String maxPlayCount = _intToString(_smartList?.filter.maxPlayCount);
@observable
late bool minYearEnabled = _smartList?.filter.minYear != null;
@observable
late String minYear = _intToString(_smartList?.filter.minYear);
@observable
late bool maxYearEnabled = _smartList?.filter.maxYear != null;
@observable
late String maxYear = _intToString(_smartList?.filter.maxYear);
@observable
late bool limitEnabled = _smartList?.filter.limit != null;
@observable
@ -108,6 +118,8 @@ abstract class _SmartListStore with Store {
reaction((_) => name, _validateName),
reaction((_) => minPlayCount, (String n) => _validateMinPlayCount(minPlayCountEnabled, n)),
reaction((_) => maxPlayCount, (String n) => _validateMaxPlayCount(maxPlayCountEnabled, n)),
reaction((_) => minYear, (String n) => _validateMinYear(minYearEnabled, n)),
reaction((_) => maxYear, (String n) => _validateMaxYear(maxYearEnabled, n)),
reaction((_) => limit, (String n) => _validateLimit(limitEnabled, n)),
reaction((_) => selectedArtists, (_) => print(selectedArtists)),
];
@ -123,6 +135,8 @@ abstract class _SmartListStore with Store {
_validateName(name);
_validateMinPlayCount(minPlayCountEnabled, minPlayCount);
_validateMaxPlayCount(maxPlayCountEnabled, maxPlayCount);
_validateMinYear(minYearEnabled, minYear);
_validateMaxYear(maxYearEnabled, maxYear);
_validateLimit(limitEnabled, limit);
}
@ -138,6 +152,14 @@ abstract class _SmartListStore with Store {
error.maxPlayCount = _validateNumber(enabled, number);
}
void _validateMinYear(bool enabled, String number) {
error.minYear = _validateNumber(enabled, number);
}
void _validateMaxYear(bool enabled, String number) {
error.maxYear = _validateNumber(enabled, number);
}
void _validateLimit(bool enabled, String number) {
error.limit = _validateNumber(enabled, number);
}
@ -157,6 +179,8 @@ abstract class _SmartListStore with Store {
excludeArtists: excludeArtists,
minPlayCount: minPlayCountEnabled ? int.tryParse(minPlayCount) : null,
maxPlayCount: maxPlayCountEnabled ? int.tryParse(maxPlayCount) : null,
minYear: minYearEnabled ? int.tryParse(minYear) : null,
maxYear: maxYearEnabled ? int.tryParse(maxYear) : null,
minLikeCount: minLikeCount,
maxLikeCount: maxLikeCount,
excludeBlocked: excludeBlocked,
@ -181,6 +205,8 @@ abstract class _SmartListStore with Store {
excludeArtists: excludeArtists,
minPlayCount: minPlayCountEnabled ? int.tryParse(minPlayCount) : null,
maxPlayCount: maxPlayCountEnabled ? int.tryParse(maxPlayCount) : null,
minYear: minYearEnabled ? int.tryParse(minYear) : null,
maxYear: maxYearEnabled ? int.tryParse(maxYear) : null,
minLikeCount: minLikeCount,
maxLikeCount: maxLikeCount,
excludeBlocked: excludeBlocked,
@ -207,12 +233,18 @@ abstract class _FormErrorState with Store {
@observable
String? maxPlayCount;
@observable
String? minYear;
@observable
String? maxYear;
@observable
String? limit;
@computed
bool get hasErrors =>
name != null || minPlayCount != null || maxPlayCount != null || limit != null;
name != null || minPlayCount != null || maxPlayCount != null || minYear != null || maxYear != null || limit != null;
}
class OrderEntry {
@ -235,6 +267,8 @@ List<OrderEntry> _createOrderState(OrderBy? orderBy) {
OrderCriterion.likeCount: 'Like count',
OrderCriterion.playCount: 'Play count',
OrderCriterion.artistName: 'Artist name',
OrderCriterion.year: 'Year',
OrderCriterion.timeAdded: 'Time added',
};
final defaultState = [
@ -246,6 +280,10 @@ List<OrderEntry> _createOrderState(OrderBy? orderBy) {
descriptions[OrderCriterion.playCount]!),
OrderEntry(false, OrderCriterion.artistName, OrderDirection.ascending,
descriptions[OrderCriterion.artistName]!),
OrderEntry(false, OrderCriterion.year, OrderDirection.ascending,
descriptions[OrderCriterion.year]!),
OrderEntry(false, OrderCriterion.timeAdded, OrderDirection.ascending,
descriptions[OrderCriterion.timeAdded]!),
];
if (orderBy == null) {

View file

@ -116,6 +116,66 @@ mixin _$SmartListFormStore on _SmartListStore, Store {
});
}
final _$minYearEnabledAtom = Atom(name: '_SmartListStore.minYearEnabled');
@override
bool get minYearEnabled {
_$minYearEnabledAtom.reportRead();
return super.minYearEnabled;
}
@override
set minYearEnabled(bool value) {
_$minYearEnabledAtom.reportWrite(value, super.minYearEnabled, () {
super.minYearEnabled = value;
});
}
final _$minYearAtom = Atom(name: '_SmartListStore.minYear');
@override
String get minYear {
_$minYearAtom.reportRead();
return super.minYear;
}
@override
set minYear(String value) {
_$minYearAtom.reportWrite(value, super.minYear, () {
super.minYear = value;
});
}
final _$maxYearEnabledAtom = Atom(name: '_SmartListStore.maxYearEnabled');
@override
bool get maxYearEnabled {
_$maxYearEnabledAtom.reportRead();
return super.maxYearEnabled;
}
@override
set maxYearEnabled(bool value) {
_$maxYearEnabledAtom.reportWrite(value, super.maxYearEnabled, () {
super.maxYearEnabled = value;
});
}
final _$maxYearAtom = Atom(name: '_SmartListStore.maxYear');
@override
String get maxYear {
_$maxYearAtom.reportRead();
return super.maxYear;
}
@override
set maxYear(String value) {
_$maxYearAtom.reportWrite(value, super.maxYear, () {
super.maxYear = value;
});
}
final _$limitEnabledAtom = Atom(name: '_SmartListStore.limitEnabled');
@override
@ -274,6 +334,10 @@ minPlayCountEnabled: ${minPlayCountEnabled},
minPlayCount: ${minPlayCount},
maxPlayCountEnabled: ${maxPlayCountEnabled},
maxPlayCount: ${maxPlayCount},
minYearEnabled: ${minYearEnabled},
minYear: ${minYear},
maxYearEnabled: ${maxYearEnabled},
maxYear: ${maxYear},
limitEnabled: ${limitEnabled},
limit: ${limit},
excludeBlocked: ${excludeBlocked},
@ -338,6 +402,36 @@ mixin _$FormErrorState on _FormErrorState, Store {
});
}
final _$minYearAtom = Atom(name: '_FormErrorState.minYear');
@override
String? get minYear {
_$minYearAtom.reportRead();
return super.minYear;
}
@override
set minYear(String? value) {
_$minYearAtom.reportWrite(value, super.minYear, () {
super.minYear = value;
});
}
final _$maxYearAtom = Atom(name: '_FormErrorState.maxYear');
@override
String? get maxYear {
_$maxYearAtom.reportRead();
return super.maxYear;
}
@override
set maxYear(String? value) {
_$maxYearAtom.reportWrite(value, super.maxYear, () {
super.maxYear = value;
});
}
final _$limitAtom = Atom(name: '_FormErrorState.limit');
@override
@ -359,6 +453,8 @@ mixin _$FormErrorState on _FormErrorState, Store {
name: ${name},
minPlayCount: ${minPlayCount},
maxPlayCount: ${maxPlayCount},
minYear: ${minYear},
maxYear: ${maxYear},
limit: ${limit},
hasErrors: ${hasErrors}
''';

View file

@ -49,6 +49,7 @@ class SongInfo extends StatelessWidget {
leadingWidth: _LEADING_WIDTH),
InfoRow(
leading: 'Disc number:', trailing: '${song.discNumber}', leadingWidth: _LEADING_WIDTH),
InfoRow(leading: 'Time added:', trailing: '${song.timeAdded}', leadingWidth: _LEADING_WIDTH),
InfoRow(leading: 'Is blocked:', trailing: '${song.blocked}', leadingWidth: _LEADING_WIDTH),
InfoRow(leading: 'Likes:', trailing: '${song.likeCount}', leadingWidth: _LEADING_WIDTH),
InfoRow(

View file

@ -104,6 +104,11 @@ class MusicDataDao extends DatabaseAccessor<MoorDatabase>
if (filter.maxPlayCount != null)
query = query..where((tbl) => tbl.playCount.isSmallerOrEqualValue(filter.maxPlayCount));
if (filter.minYear != null)
query = query..where((tbl) => tbl.year.isBiggerOrEqualValue(filter.minYear));
if (filter.maxYear != null)
query = query..where((tbl) => tbl.year.isSmallerOrEqualValue(filter.maxYear));
if (filter.excludeBlocked) query = query..where((tbl) => tbl.blocked.not());
if (filter.artists.isNotEmpty) {

View file

@ -55,6 +55,9 @@ class SettingsDao extends DatabaseAccessor<MoorDatabase>
maxPlayCount: Value(filter.maxPlayCount),
minLikeCount: Value(filter.minLikeCount),
maxLikeCount: Value(filter.maxLikeCount),
minYear: Value(filter.minYear),
maxYear: Value(filter.maxYear),
excludeBlocked: Value(filter.excludeBlocked),
limit: Value(filter.limit),
orderCriteria: Value(orderCriteria),
orderDirections: Value(orderDirections),

View file

@ -52,8 +52,8 @@ class Songs extends Table {
IntColumn get skipCount => integer().withDefault(const Constant(0))();
IntColumn get playCount => integer().withDefault(const Constant(0))();
BoolColumn get present => boolean().withDefault(const Constant(true))();
IntColumn get timeAdded =>
integer().withDefault(Constant(DateTime.now().millisecondsSinceEpoch))();
DateTimeColumn get timeAdded =>
dateTime().withDefault(currentDateAndTime)();
TextColumn get previous => text().withDefault(const Constant(''))();
TextColumn get next => text().withDefault(const Constant(''))();
@ -177,7 +177,7 @@ class MoorDatabase extends _$MoorDatabase {
MoorDatabase.connect(DatabaseConnection connection) : super.connect(connection);
@override
int get schemaVersion => 2;
int get schemaVersion => 3;
@override
MigrationStrategy get migration => MigrationStrategy(beforeOpen: (details) async {
@ -208,6 +208,16 @@ class MoorDatabase extends _$MoorDatabase {
}
});
}
if (from < 3) {
await m.alterTable(
TableMigration(
songs,
columnTransformer: {
songs.timeAdded: currentDateAndTime,
}
),
);
}
});
}

View file

@ -1756,7 +1756,7 @@ class MoorSong extends DataClass implements Insertable<MoorSong> {
final int skipCount;
final int playCount;
final bool present;
final int timeAdded;
final DateTime timeAdded;
final String previous;
final String next;
MoorSong(
@ -1812,7 +1812,7 @@ class MoorSong extends DataClass implements Insertable<MoorSong> {
.mapFromDatabaseResponse(data['${effectivePrefix}play_count'])!,
present: const BoolType()
.mapFromDatabaseResponse(data['${effectivePrefix}present'])!,
timeAdded: const IntType()
timeAdded: const DateTimeType()
.mapFromDatabaseResponse(data['${effectivePrefix}time_added'])!,
previous: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}previous'])!,
@ -1842,7 +1842,7 @@ class MoorSong extends DataClass implements Insertable<MoorSong> {
map['skip_count'] = Variable<int>(skipCount);
map['play_count'] = Variable<int>(playCount);
map['present'] = Variable<bool>(present);
map['time_added'] = Variable<int>(timeAdded);
map['time_added'] = Variable<DateTime>(timeAdded);
map['previous'] = Variable<String>(previous);
map['next'] = Variable<String>(next);
return map;
@ -1892,7 +1892,7 @@ class MoorSong extends DataClass implements Insertable<MoorSong> {
skipCount: serializer.fromJson<int>(json['skipCount']),
playCount: serializer.fromJson<int>(json['playCount']),
present: serializer.fromJson<bool>(json['present']),
timeAdded: serializer.fromJson<int>(json['timeAdded']),
timeAdded: serializer.fromJson<DateTime>(json['timeAdded']),
previous: serializer.fromJson<String>(json['previous']),
next: serializer.fromJson<String>(json['next']),
);
@ -1916,7 +1916,7 @@ class MoorSong extends DataClass implements Insertable<MoorSong> {
'skipCount': serializer.toJson<int>(skipCount),
'playCount': serializer.toJson<int>(playCount),
'present': serializer.toJson<bool>(present),
'timeAdded': serializer.toJson<int>(timeAdded),
'timeAdded': serializer.toJson<DateTime>(timeAdded),
'previous': serializer.toJson<String>(previous),
'next': serializer.toJson<String>(next),
};
@ -1938,7 +1938,7 @@ class MoorSong extends DataClass implements Insertable<MoorSong> {
int? skipCount,
int? playCount,
bool? present,
int? timeAdded,
DateTime? timeAdded,
String? previous,
String? next}) =>
MoorSong(
@ -2064,7 +2064,7 @@ class SongsCompanion extends UpdateCompanion<MoorSong> {
final Value<int> skipCount;
final Value<int> playCount;
final Value<bool> present;
final Value<int> timeAdded;
final Value<DateTime> timeAdded;
final Value<String> previous;
final Value<String> next;
const SongsCompanion({
@ -2130,7 +2130,7 @@ class SongsCompanion extends UpdateCompanion<MoorSong> {
Expression<int>? skipCount,
Expression<int>? playCount,
Expression<bool>? present,
Expression<int>? timeAdded,
Expression<DateTime>? timeAdded,
Expression<String>? previous,
Expression<String>? next,
}) {
@ -2172,7 +2172,7 @@ class SongsCompanion extends UpdateCompanion<MoorSong> {
Value<int>? skipCount,
Value<int>? playCount,
Value<bool>? present,
Value<int>? timeAdded,
Value<DateTime>? timeAdded,
Value<String>? previous,
Value<String>? next}) {
return SongsCompanion(
@ -2246,7 +2246,7 @@ class SongsCompanion extends UpdateCompanion<MoorSong> {
map['present'] = Variable<bool>(present.value);
}
if (timeAdded.present) {
map['time_added'] = Variable<int>(timeAdded.value);
map['time_added'] = Variable<DateTime>(timeAdded.value);
}
if (previous.present) {
map['previous'] = Variable<String>(previous.value);
@ -2441,10 +2441,10 @@ class $SongsTable extends Songs with TableInfo<$SongsTable, MoorSong> {
final VerificationMeta _timeAddedMeta = const VerificationMeta('timeAdded');
@override
late final GeneratedIntColumn timeAdded = _constructTimeAdded();
GeneratedIntColumn _constructTimeAdded() {
return GeneratedIntColumn('time_added', $tableName, false,
defaultValue: Constant(DateTime.now().millisecondsSinceEpoch));
late final GeneratedDateTimeColumn timeAdded = _constructTimeAdded();
GeneratedDateTimeColumn _constructTimeAdded() {
return GeneratedDateTimeColumn('time_added', $tableName, false,
defaultValue: currentDateAndTime);
}
final VerificationMeta _previousMeta = const VerificationMeta('previous');

View file

@ -3,6 +3,7 @@ import 'package:moor/moor.dart';
import '../../domain/entities/album.dart';
import '../datasources/moor_database.dart';
import '../utils.dart';
import 'default_values.dart';
class AlbumModel extends Album {
@ -39,7 +40,7 @@ class AlbumModel extends Album {
title: tag.album ?? DEF_ALBUM,
artist: artist ?? DEF_ARTIST,
albumArtPath: albumArtPath,
year: tag.year == null ? null : _parseYear(tag.year!),
year: tag.year == null ? null : parseYear(tag.year!),
);
}
@ -71,18 +72,6 @@ class AlbumModel extends Album {
albumArtPath: Value(albumArtPath),
year: Value(pubYear),
);
static int? _parseYear(String yearString) {
if (yearString == '') {
return null;
}
try {
return int.parse(yearString);
} on FormatException {
return int.parse(yearString.split('-')[0]);
}
}
}
class AlbumOfDay {

View file

@ -30,6 +30,8 @@ class SmartListModel extends SmartList {
maxLikeCount: moorSmartList.maxLikeCount,
minPlayCount: moorSmartList.minPlayCount,
maxPlayCount: moorSmartList.maxPlayCount,
minYear: moorSmartList.minYear,
maxYear: moorSmartList.maxYear,
excludeBlocked: moorSmartList.excludeBlocked,
limit: moorSmartList.limit,
);
@ -59,6 +61,8 @@ class SmartListModel extends SmartList {
maxPlayCount: m.Value(filter.maxPlayCount),
minLikeCount: m.Value(filter.minLikeCount),
maxLikeCount: m.Value(filter.maxLikeCount),
minYear: m.Value(filter.minYear),
maxYear: m.Value(filter.maxYear),
excludeBlocked: m.Value(filter.excludeBlocked),
limit: m.Value(filter.limit),
orderCriteria: m.Value(orderBy.orderCriteria.join(',')),

View file

@ -5,6 +5,7 @@ import 'package:moor/moor.dart';
import '../../domain/entities/song.dart';
import '../datasources/moor_database.dart';
import '../utils.dart';
import 'default_values.dart';
class SongModel extends Song {
@ -61,7 +62,7 @@ class SongModel extends Song {
likeCount: moorSong.likeCount,
skipCount: moorSong.skipCount,
playCount: moorSong.playCount,
timeAdded: DateTime.fromMillisecondsSinceEpoch(moorSong.timeAdded),
timeAdded: moorSong.timeAdded,
year: moorSong.year,
);
@ -88,6 +89,7 @@ class SongModel extends Song {
likeCount: 0,
playCount: 0,
skipCount: 0,
year: parseYear(tag.year),
timeAdded: DateTime.fromMillisecondsSinceEpoch(0),
);
}
@ -182,7 +184,7 @@ class SongModel extends Song {
likeCount: Value(likeCount),
skipCount: Value(skipCount),
playCount: Value(playCount),
timeAdded: Value(timeAdded.millisecondsSinceEpoch),
timeAdded: Value(timeAdded),
);
SongsCompanion toMoorInsert() => SongsCompanion(

11
lib/system/utils.dart Normal file
View file

@ -0,0 +1,11 @@
int? parseYear(String? yearString) {
if (yearString == null || yearString == '') {
return null;
}
try {
return int.parse(yearString);
} on FormatException {
return int.parse(yearString.split('-')[0]);
}
}