implemented block on skip

This commit is contained in:
Moritz Weber 2021-10-30 21:18:04 +02:00
parent 2328454610
commit 935634f2a2
26 changed files with 949 additions and 45 deletions

View file

@ -30,6 +30,8 @@ class Filter extends Equatable {
this.maxPlayCount,
required this.minLikeCount,
required this.maxLikeCount,
this.minSkipCount,
this.maxSkipCount,
this.minYear,
this.maxYear,
required this.excludeBlocked,
@ -45,6 +47,9 @@ class Filter extends Equatable {
final int minLikeCount;
final int maxLikeCount;
final int? minSkipCount;
final int? maxSkipCount;
final int? minYear;
final int? maxYear;
@ -83,6 +88,7 @@ enum OrderCriterion {
artistName,
likeCount,
playCount,
skipCount,
songTitle,
timeAdded,
year,
@ -97,6 +103,8 @@ extension OrderCriterionExtension on String {
return OrderCriterion.likeCount;
case 'OrderCriterion.playCount':
return OrderCriterion.playCount;
case 'OrderCriterion.skipCount':
return OrderCriterion.skipCount;
case 'OrderCriterion.songTitle':
return OrderCriterion.songTitle;
case 'OrderCriterion.timeAdded':

View file

@ -2,4 +2,9 @@ abstract class SettingsRepository {
Stream<List<String>> get libraryFoldersStream;
Future<void> addLibraryFolder(String? path);
Future<void> removeLibraryFolder(String? path);
Future<void> setBlockSkippedSongs(bool enabled);
Stream<bool> get isBlockSkippedSongsEnabled;
Future<void> setBlockSkippedSongsThreshold(int threshold);
Stream<int> get blockSkippedSongsThreshold;
}

View file

@ -28,6 +28,7 @@ import 'presentation/state/audio_store.dart';
import 'presentation/state/music_data_store.dart';
import 'presentation/state/navigation_store.dart';
import 'presentation/state/search_page_store.dart';
import 'presentation/state/settings_page_store.dart';
import 'presentation/state/settings_store.dart';
import 'presentation/state/smart_list_form_store.dart';
import 'presentation/state/smart_list_page_store.dart';
@ -79,16 +80,21 @@ Future<void> setupGetIt() async {
musicDataInfoRepository: getIt(),
),
);
getIt.registerLazySingleton<SettingsStore>(
() => SettingsStore(
settingsRepository: getIt(),
musicDataRepository: getIt(),
),
);
getIt.registerFactoryParam<ArtistPageStore, Artist, void>(
(Artist? artist, _) => ArtistPageStore(artist: artist!, musicDataInfoRepository: getIt()),
);
getIt.registerFactoryParam<AlbumPageStore, Album, void>(
(Album? album, _) => AlbumPageStore(album: album!, musicDataInfoRepository: getIt()),
);
getIt.registerFactory<SettingsStore>(
() => SettingsStore(
getIt.registerFactory<SettingsPageStore>(
() => SettingsPageStore(
settingsRepository: getIt(),
musicDataRepository: getIt(),
),
);
getIt.registerFactoryParam<SmartListFormStore, SmartList, void>(

View file

@ -12,8 +12,8 @@ import '../state/music_data_store.dart';
import '../state/settings_store.dart';
import '../theming.dart';
import 'playlist_page.dart';
import 'smart_list_form_page.dart';
import 'smart_list_page.dart';
import 'smart_lists_form_page.dart';
class PlaylistsPage extends StatefulWidget {
const PlaylistsPage({Key? key}) : super(key: key);

View file

@ -1,15 +1,54 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:get_it/get_it.dart';
import 'package:mobx/mobx.dart';
import '../state/music_data_store.dart';
import '../state/settings_page_store.dart';
import '../theming.dart';
import 'library_folders_page.dart';
import 'smart_lists_settings_page.dart';
class SettingsPage extends StatelessWidget {
class SettingsPage extends StatefulWidget {
const SettingsPage({Key? key}) : super(key: key);
@override
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
late SettingsPageStore settingsPageStore;
late TextEditingController _controller;
late ReactionDisposer _dispose;
@override
void initState() {
super.initState();
settingsPageStore = GetIt.I<SettingsPageStore>();
settingsPageStore.init();
settingsPageStore.setupValidations();
_controller = TextEditingController();
_controller.addListener(() {
if (_controller.text != settingsPageStore.blockSkippedSongsThreshold) {
print('ctrl listener: ${_controller.text}');
settingsPageStore.blockSkippedSongsThreshold = _controller.text;
}
});
_dispose = autorun((_) {
if (_controller.text != settingsPageStore.blockSkippedSongsThreshold) {
print('autorun: ${settingsPageStore.blockSkippedSongsThreshold}');
_controller.text = settingsPageStore.blockSkippedSongsThreshold;
}
});
}
@override
void dispose() {
super.dispose();
settingsPageStore.dispose();
_dispose();
}
@override
Widget build(BuildContext context) {
final MusicDataStore musicDataStore = GetIt.I<MusicDataStore>();
@ -65,18 +104,66 @@ class SettingsPage extends StatelessWidget {
height: 4.0,
),
const SettingsSection(text: 'Home page'),
ListTile(
title: const Text('Customize smart lists'),
trailing: const Icon(Icons.chevron_right),
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => const SmartListsSettingsPage(),
),
),
const ListTile(
title: Text('Soon (tm)'),
),
const Divider(
height: 4.0,
),
const SettingsSection(text: 'Customize playback'),
Observer(
builder: (_) {
final bool enabled = settingsPageStore.isBlockSkippedSongsEnabled;
return Column(
children: [
Padding(
padding:
const EdgeInsets.symmetric(horizontal: HORIZONTAL_PADDING, vertical: 4.0),
child: Row(
children: [
const Text('Mark skipped songs as blocked'),
const Spacer(),
Switch(
value: settingsPageStore.isBlockSkippedSongsEnabled,
onChanged: (bool value) {
print('set: $value');
settingsPageStore.isBlockSkippedSongsEnabled = value;
},
),
],
),
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: HORIZONTAL_PADDING, vertical: 4.0),
child: Row(
children: [
const Text('Minimum skip count to block songs'),
const Spacer(),
SizedBox(
width: 56.0,
child: TextFormField(
controller: _controller,
enabled: enabled,
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
onChanged: (value) {
print(value);
},
decoration: InputDecoration(
errorText: settingsPageStore.error.skipCountThreshold,
errorStyle: const TextStyle(height: 0, fontSize: 0),
),
),
),
],
),
),
],
);
},
),
],
),
),

View file

@ -176,7 +176,7 @@ class _SmartListFormPageState extends State<SmartListFormPage> {
child: TextFormField(
enabled: store.maxPlayCountEnabled,
keyboardType: TextInputType.number,
initialValue: store.maxPlayCount.toString(),
initialValue: store.maxPlayCount,
textAlign: TextAlign.center,
onChanged: (value) {
store.maxPlayCount = value;
@ -193,6 +193,74 @@ class _SmartListFormPageState extends State<SmartListFormPage> {
),
),
const SizedBox(height: 24.0),
Padding(
padding: const EdgeInsets.symmetric(horizontal: HORIZONTAL_PADDING),
child: Observer(
builder: (_) {
return Row(
children: [
Switch(
value: store.minSkipCountEnabled,
onChanged: (bool value) => store.minSkipCountEnabled = value,
),
const Text('Minimum skip count'),
const Spacer(),
SizedBox(
width: 36.0,
child: TextFormField(
enabled: store.minSkipCountEnabled,
keyboardType: TextInputType.number,
initialValue: store.minSkipCount,
onChanged: (value) {
store.minSkipCount = value;
},
textAlign: TextAlign.center,
decoration: InputDecoration(
errorText: store.error.minSkipCount,
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.maxSkipCountEnabled,
onChanged: (bool value) => store.maxSkipCountEnabled = value,
),
const Text('Maximum skip count'),
const Spacer(),
SizedBox(
width: 36.0,
child: TextFormField(
enabled: store.maxSkipCountEnabled,
keyboardType: TextInputType.number,
initialValue: store.maxSkipCount,
textAlign: TextAlign.center,
onChanged: (value) {
store.maxSkipCount = value;
},
decoration: InputDecoration(
errorText: store.error.maxSkipCount,
errorStyle: const TextStyle(height: 0, fontSize: 0),
),
),
),
],
);
},
),
),
const SizedBox(height: 24.0),
Padding(
padding: const EdgeInsets.symmetric(horizontal: HORIZONTAL_PADDING),
child: Observer(
@ -244,7 +312,7 @@ class _SmartListFormPageState extends State<SmartListFormPage> {
child: TextFormField(
enabled: store.maxYearEnabled,
keyboardType: TextInputType.number,
initialValue: store.maxYear.toString(),
initialValue: store.maxYear,
textAlign: TextAlign.center,
onChanged: (value) {
store.maxYear = value;
@ -278,7 +346,7 @@ class _SmartListFormPageState extends State<SmartListFormPage> {
child: TextFormField(
enabled: store.limitEnabled,
keyboardType: TextInputType.number,
initialValue: store.limit.toString(),
initialValue: store.limit,
textAlign: TextAlign.center,
onChanged: (value) {
store.limit = value;

View file

@ -9,7 +9,7 @@ import '../state/smart_list_page_store.dart';
import '../theming.dart';
import '../widgets/song_bottom_sheet.dart';
import '../widgets/song_list_tile.dart';
import 'smart_lists_form_page.dart';
import 'smart_list_form_page.dart';
class SmartListPage extends StatefulWidget {
const SmartListPage({Key? key, required this.smartList}) : super(key: key);

View file

@ -5,8 +5,9 @@ import 'package:get_it/get_it.dart';
import '../../domain/entities/smart_list.dart';
import '../state/settings_store.dart';
import '../theming.dart';
import 'smart_lists_form_page.dart';
import 'smart_list_form_page.dart';
// TODO: currently unused
class SmartListsSettingsPage extends StatelessWidget {
const SmartListsSettingsPage({Key? key}) : super(key: key);

View file

@ -6,6 +6,7 @@ import 'package:mobx/mobx.dart';
import '../../domain/entities/song.dart';
import '../state/audio_store.dart';
import '../state/music_data_store.dart';
import '../state/settings_store.dart';
import '../widgets/song_bottom_sheet.dart';
import '../widgets/song_list_tile.dart';
@ -22,12 +23,15 @@ class _SongsPageState extends State<SongsPage> with AutomaticKeepAliveClientMixi
print('SongsPage.build');
final MusicDataStore musicDataStore = GetIt.I<MusicDataStore>();
final AudioStore audioStore = GetIt.I<AudioStore>();
final SettingsStore settingsStore = GetIt.I<SettingsStore>();
super.build(context);
return Observer(builder: (_) {
print('SongsPage.build -> Observer.builder');
final songStream = musicDataStore.songStream;
final isBlockSkippedSongsEnabled = settingsStore.isBlockSkippedSongsEnabled.first;
final blockSkippedSongsThreshold = settingsStore.blockSkippedSongsThreshold.first;
switch (songStream.status) {
case StreamStatus.active:
@ -43,6 +47,8 @@ class _SongsPageState extends State<SongsPage> with AutomaticKeepAliveClientMixi
subtitle: Subtitle.artistAlbum,
onTap: () => audioStore.playSong(index, songs),
onTapMore: () => SongBottomSheet()(song, context),
isBlockSkippedSongsEnabled: isBlockSkippedSongsEnabled.value,
blockSkippedSongsThreshold: blockSkippedSongsThreshold.value,
);
},
separatorBuilder: (BuildContext context, int index) => const SizedBox(

View file

@ -0,0 +1,84 @@
import 'package:mobx/mobx.dart';
import '../../domain/repositories/settings_repository.dart';
import '../utils.dart';
part 'settings_page_store.g.dart';
class SettingsPageStore extends _SettingsPageStore with _$SettingsPageStore {
SettingsPageStore({
required SettingsRepository settingsRepository,
}) : super(settingsRepository);
}
abstract class _SettingsPageStore with Store {
_SettingsPageStore(
this._settingsRepository,
);
final SettingsRepository _settingsRepository;
final FormErrorState error = FormErrorState();
@observable
bool isBlockSkippedSongsEnabled = false;
@observable
String blockSkippedSongsThreshold = '-1';
late List<ReactionDisposer> _disposers;
Future<void> addLibraryFolder(String? path) async {
await _settingsRepository.addLibraryFolder(path);
}
Future<void> removeLibraryFolder(String? path) async {
await _settingsRepository.removeLibraryFolder(path);
}
@action
Future<void> init() async {
isBlockSkippedSongsEnabled = await _settingsRepository.isBlockSkippedSongsEnabled.first;
blockSkippedSongsThreshold = (await _settingsRepository.blockSkippedSongsThreshold.first).toString();
}
void setupValidations() {
_disposers = [
reaction((_) => blockSkippedSongsThreshold,
(String n) => _validateSkipCountThreshold(isBlockSkippedSongsEnabled, n)),
reaction((_) => isBlockSkippedSongsEnabled, _validateBlockSkipCountEnabled),
];
}
void dispose() {
for (final d in _disposers) {
d();
}
}
void validateAll() {
_validateSkipCountThreshold(isBlockSkippedSongsEnabled, blockSkippedSongsThreshold);
}
void _validateSkipCountThreshold(bool enabled, String number) {
error.skipCountThreshold = validateNumber(enabled, number);
if (!error.hasErrors) {
final val = int.parse(blockSkippedSongsThreshold);
_settingsRepository.setBlockSkippedSongsThreshold(val);
}
}
void _validateBlockSkipCountEnabled(bool enabled) {
_settingsRepository.setBlockSkippedSongs(enabled);
}
}
class FormErrorState = _FormErrorState with _$FormErrorState;
abstract class _FormErrorState with Store {
@observable
String? skipCountThreshold;
@computed
bool get hasErrors => skipCountThreshold != null;
}

View file

@ -0,0 +1,94 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'settings_page_store.dart';
// **************************************************************************
// StoreGenerator
// **************************************************************************
// ignore_for_file: non_constant_identifier_names, unnecessary_brace_in_string_interps, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic
mixin _$SettingsPageStore on _SettingsPageStore, Store {
final _$isBlockSkippedSongsEnabledAtom =
Atom(name: '_SettingsPageStore.isBlockSkippedSongsEnabled');
@override
bool get isBlockSkippedSongsEnabled {
_$isBlockSkippedSongsEnabledAtom.reportRead();
return super.isBlockSkippedSongsEnabled;
}
@override
set isBlockSkippedSongsEnabled(bool value) {
_$isBlockSkippedSongsEnabledAtom
.reportWrite(value, super.isBlockSkippedSongsEnabled, () {
super.isBlockSkippedSongsEnabled = value;
});
}
final _$blockSkippedSongsThresholdAtom =
Atom(name: '_SettingsPageStore.blockSkippedSongsThreshold');
@override
String get blockSkippedSongsThreshold {
_$blockSkippedSongsThresholdAtom.reportRead();
return super.blockSkippedSongsThreshold;
}
@override
set blockSkippedSongsThreshold(String value) {
_$blockSkippedSongsThresholdAtom
.reportWrite(value, super.blockSkippedSongsThreshold, () {
super.blockSkippedSongsThreshold = value;
});
}
final _$initAsyncAction = AsyncAction('_SettingsPageStore.init');
@override
Future<void> init() {
return _$initAsyncAction.run(() => super.init());
}
@override
String toString() {
return '''
isBlockSkippedSongsEnabled: ${isBlockSkippedSongsEnabled},
blockSkippedSongsThreshold: ${blockSkippedSongsThreshold}
''';
}
}
mixin _$FormErrorState on _FormErrorState, Store {
Computed<bool>? _$hasErrorsComputed;
@override
bool get hasErrors =>
(_$hasErrorsComputed ??= Computed<bool>(() => super.hasErrors,
name: '_FormErrorState.hasErrors'))
.value;
final _$skipCountThresholdAtom =
Atom(name: '_FormErrorState.skipCountThreshold');
@override
String? get skipCountThreshold {
_$skipCountThresholdAtom.reportRead();
return super.skipCountThreshold;
}
@override
set skipCountThreshold(String? value) {
_$skipCountThresholdAtom.reportWrite(value, super.skipCountThreshold, () {
super.skipCountThreshold = value;
});
}
@override
String toString() {
return '''
skipCountThreshold: ${skipCountThreshold},
hasErrors: ${hasErrors}
''';
}
}

View file

@ -30,6 +30,12 @@ abstract class _SettingsStore with Store {
late ObservableStream<List<String>> libraryFoldersStream =
_settingsRepository.libraryFoldersStream.asObservable(initialValue: []);
@observable
late ObservableStream<bool> isBlockSkippedSongsEnabled = _settingsRepository.isBlockSkippedSongsEnabled.asObservable();
@observable
late ObservableStream<int> blockSkippedSongsThreshold = _settingsRepository.blockSkippedSongsThreshold.asObservable();
Future<void> addLibraryFolder(String? path) async {
await _settingsRepository.addLibraryFolder(path);
}

View file

@ -41,11 +41,47 @@ mixin _$SettingsStore on _SettingsStore, Store {
});
}
final _$isBlockSkippedSongsEnabledAtom =
Atom(name: '_SettingsStore.isBlockSkippedSongsEnabled');
@override
ObservableStream<bool> get isBlockSkippedSongsEnabled {
_$isBlockSkippedSongsEnabledAtom.reportRead();
return super.isBlockSkippedSongsEnabled;
}
@override
set isBlockSkippedSongsEnabled(ObservableStream<bool> value) {
_$isBlockSkippedSongsEnabledAtom
.reportWrite(value, super.isBlockSkippedSongsEnabled, () {
super.isBlockSkippedSongsEnabled = value;
});
}
final _$blockSkippedSongsThresholdAtom =
Atom(name: '_SettingsStore.blockSkippedSongsThreshold');
@override
ObservableStream<int> get blockSkippedSongsThreshold {
_$blockSkippedSongsThresholdAtom.reportRead();
return super.blockSkippedSongsThreshold;
}
@override
set blockSkippedSongsThreshold(ObservableStream<int> value) {
_$blockSkippedSongsThresholdAtom
.reportWrite(value, super.blockSkippedSongsThreshold, () {
super.blockSkippedSongsThreshold = value;
});
}
@override
String toString() {
return '''
smartListsStream: ${smartListsStream},
libraryFoldersStream: ${libraryFoldersStream}
libraryFoldersStream: ${libraryFoldersStream},
isBlockSkippedSongsEnabled: ${isBlockSkippedSongsEnabled},
blockSkippedSongsThreshold: ${blockSkippedSongsThreshold}
''';
}
}

View file

@ -3,6 +3,7 @@ import 'package:mobx/mobx.dart';
import '../../domain/entities/artist.dart';
import '../../domain/entities/smart_list.dart';
import '../../domain/repositories/music_data_repository.dart';
import '../utils.dart';
part 'smart_list_form_store.g.dart';
@ -42,6 +43,16 @@ abstract class _SmartListStore with Store {
@observable
late String maxPlayCount = _intToString(_smartList?.filter.maxPlayCount);
@observable
late bool minSkipCountEnabled = _smartList?.filter.minSkipCount != null;
@observable
late String minSkipCount = _intToString(_smartList?.filter.minSkipCount);
@observable
late bool maxSkipCountEnabled = _smartList?.filter.maxSkipCount != null;
@observable
late String maxSkipCount = _intToString(_smartList?.filter.maxSkipCount);
@observable
late bool minYearEnabled = _smartList?.filter.minYear != null;
@observable
@ -118,6 +129,8 @@ abstract class _SmartListStore with Store {
reaction((_) => name, _validateName),
reaction((_) => minPlayCount, (String n) => _validateMinPlayCount(minPlayCountEnabled, n)),
reaction((_) => maxPlayCount, (String n) => _validateMaxPlayCount(maxPlayCountEnabled, n)),
reaction((_) => minSkipCount, (String n) => _validateMinSkipCount(minSkipCountEnabled, n)),
reaction((_) => maxSkipCount, (String n) => _validateMaxSkipCount(maxSkipCountEnabled, n)),
reaction((_) => minYear, (String n) => _validateMinYear(minYearEnabled, n)),
reaction((_) => maxYear, (String n) => _validateMaxYear(maxYearEnabled, n)),
reaction((_) => limit, (String n) => _validateLimit(limitEnabled, n)),
@ -135,6 +148,8 @@ abstract class _SmartListStore with Store {
_validateName(name);
_validateMinPlayCount(minPlayCountEnabled, minPlayCount);
_validateMaxPlayCount(maxPlayCountEnabled, maxPlayCount);
_validateMinSkipCount(minSkipCountEnabled, minSkipCount);
_validateMaxSkipCount(maxSkipCountEnabled, maxSkipCount);
_validateMinYear(minYearEnabled, minYear);
_validateMaxYear(maxYearEnabled, maxYear);
_validateLimit(limitEnabled, limit);
@ -145,29 +160,34 @@ abstract class _SmartListStore with Store {
}
void _validateMinPlayCount(bool enabled, String number) {
error.minPlayCount = _validateNumber(enabled, number);
error.minPlayCount = validateNumber(enabled, number);
}
void _validateMaxPlayCount(bool enabled, String number) {
error.maxPlayCount = _validateNumber(enabled, number);
error.maxPlayCount = validateNumber(enabled, number);
}
void _validateMinSkipCount(bool enabled, String number) {
error.minSkipCount = validateNumber(enabled, number);
}
void _validateMaxSkipCount(bool enabled, String number) {
error.maxSkipCount = validateNumber(enabled, number);
}
void _validateMinYear(bool enabled, String number) {
error.minYear = _validateNumber(enabled, number);
error.minYear = validateNumber(enabled, number);
}
void _validateMaxYear(bool enabled, String number) {
error.maxYear = _validateNumber(enabled, number);
error.maxYear = validateNumber(enabled, number);
}
void _validateLimit(bool enabled, String number) {
error.limit = _validateNumber(enabled, number);
error.limit = validateNumber(enabled, number);
}
String? _validateNumber(bool enabled, String number) {
if (!enabled) return null;
return int.tryParse(number) == null ? 'Error' : null;
}
Future<void> _createSmartList() async {
await _musicDataRepository.insertSmartList(
@ -177,6 +197,8 @@ abstract class _SmartListStore with Store {
excludeArtists: excludeArtists,
minPlayCount: minPlayCountEnabled ? int.tryParse(minPlayCount) : null,
maxPlayCount: maxPlayCountEnabled ? int.tryParse(maxPlayCount) : null,
minSkipCount: minSkipCountEnabled ? int.tryParse(minSkipCount) : null,
maxSkipCount: maxSkipCountEnabled ? int.tryParse(maxSkipCount) : null,
minYear: minYearEnabled ? int.tryParse(minYear) : null,
maxYear: maxYearEnabled ? int.tryParse(maxYear) : null,
minLikeCount: minLikeCount,
@ -201,6 +223,8 @@ abstract class _SmartListStore with Store {
excludeArtists: excludeArtists,
minPlayCount: minPlayCountEnabled ? int.tryParse(minPlayCount) : null,
maxPlayCount: maxPlayCountEnabled ? int.tryParse(maxPlayCount) : null,
minSkipCount: minSkipCountEnabled ? int.tryParse(minSkipCount) : null,
maxSkipCount: maxSkipCountEnabled ? int.tryParse(maxSkipCount) : null,
minYear: minYearEnabled ? int.tryParse(minYear) : null,
maxYear: maxYearEnabled ? int.tryParse(maxYear) : null,
minLikeCount: minLikeCount,
@ -229,6 +253,12 @@ abstract class _FormErrorState with Store {
@observable
String? maxPlayCount;
@observable
String? minSkipCount;
@observable
String? maxSkipCount;
@observable
String? minYear;
@ -267,6 +297,7 @@ List<OrderEntry> _createOrderState(OrderBy? orderBy) {
OrderCriterion.songTitle: 'Song title',
OrderCriterion.likeCount: 'Like count',
OrderCriterion.playCount: 'Play count',
OrderCriterion.skipCount: 'Skip count',
OrderCriterion.artistName: 'Artist name',
OrderCriterion.year: 'Year',
OrderCriterion.timeAdded: 'Time added',
@ -279,6 +310,8 @@ List<OrderEntry> _createOrderState(OrderBy? orderBy) {
descriptions[OrderCriterion.likeCount]!),
OrderEntry(false, OrderCriterion.playCount, OrderDirection.ascending,
descriptions[OrderCriterion.playCount]!),
OrderEntry(false, OrderCriterion.skipCount, OrderDirection.ascending,
descriptions[OrderCriterion.skipCount]!),
OrderEntry(false, OrderCriterion.artistName, OrderDirection.ascending,
descriptions[OrderCriterion.artistName]!),
OrderEntry(

View file

@ -116,6 +116,68 @@ mixin _$SmartListFormStore on _SmartListStore, Store {
});
}
final _$minSkipCountEnabledAtom =
Atom(name: '_SmartListStore.minSkipCountEnabled');
@override
bool get minSkipCountEnabled {
_$minSkipCountEnabledAtom.reportRead();
return super.minSkipCountEnabled;
}
@override
set minSkipCountEnabled(bool value) {
_$minSkipCountEnabledAtom.reportWrite(value, super.minSkipCountEnabled, () {
super.minSkipCountEnabled = value;
});
}
final _$minSkipCountAtom = Atom(name: '_SmartListStore.minSkipCount');
@override
String get minSkipCount {
_$minSkipCountAtom.reportRead();
return super.minSkipCount;
}
@override
set minSkipCount(String value) {
_$minSkipCountAtom.reportWrite(value, super.minSkipCount, () {
super.minSkipCount = value;
});
}
final _$maxSkipCountEnabledAtom =
Atom(name: '_SmartListStore.maxSkipCountEnabled');
@override
bool get maxSkipCountEnabled {
_$maxSkipCountEnabledAtom.reportRead();
return super.maxSkipCountEnabled;
}
@override
set maxSkipCountEnabled(bool value) {
_$maxSkipCountEnabledAtom.reportWrite(value, super.maxSkipCountEnabled, () {
super.maxSkipCountEnabled = value;
});
}
final _$maxSkipCountAtom = Atom(name: '_SmartListStore.maxSkipCount');
@override
String get maxSkipCount {
_$maxSkipCountAtom.reportRead();
return super.maxSkipCount;
}
@override
set maxSkipCount(String value) {
_$maxSkipCountAtom.reportWrite(value, super.maxSkipCount, () {
super.maxSkipCount = value;
});
}
final _$minYearEnabledAtom = Atom(name: '_SmartListStore.minYearEnabled');
@override
@ -334,6 +396,10 @@ minPlayCountEnabled: ${minPlayCountEnabled},
minPlayCount: ${minPlayCount},
maxPlayCountEnabled: ${maxPlayCountEnabled},
maxPlayCount: ${maxPlayCount},
minSkipCountEnabled: ${minSkipCountEnabled},
minSkipCount: ${minSkipCount},
maxSkipCountEnabled: ${maxSkipCountEnabled},
maxSkipCount: ${maxSkipCount},
minYearEnabled: ${minYearEnabled},
minYear: ${minYear},
maxYearEnabled: ${maxYearEnabled},
@ -402,6 +468,36 @@ mixin _$FormErrorState on _FormErrorState, Store {
});
}
final _$minSkipCountAtom = Atom(name: '_FormErrorState.minSkipCount');
@override
String? get minSkipCount {
_$minSkipCountAtom.reportRead();
return super.minSkipCount;
}
@override
set minSkipCount(String? value) {
_$minSkipCountAtom.reportWrite(value, super.minSkipCount, () {
super.minSkipCount = value;
});
}
final _$maxSkipCountAtom = Atom(name: '_FormErrorState.maxSkipCount');
@override
String? get maxSkipCount {
_$maxSkipCountAtom.reportRead();
return super.maxSkipCount;
}
@override
set maxSkipCount(String? value) {
_$maxSkipCountAtom.reportWrite(value, super.maxSkipCount, () {
super.maxSkipCount = value;
});
}
final _$minYearAtom = Atom(name: '_FormErrorState.minYear');
@override
@ -453,6 +549,8 @@ mixin _$FormErrorState on _FormErrorState, Store {
name: ${name},
minPlayCount: ${minPlayCount},
maxPlayCount: ${maxPlayCount},
minSkipCount: ${minSkipCount},
maxSkipCount: ${maxSkipCount},
minYear: ${minYear},
maxYear: ${maxYear},
limit: ${limit},

View file

@ -35,6 +35,8 @@ ThemeData theme() => ThemeData(
primary: LIGHT1,
),
),
progressIndicatorTheme: const ProgressIndicatorThemeData(color: LIGHT2),
sliderTheme: const SliderThemeData(activeTrackColor: LIGHT2, thumbColor: LIGHT2, inactiveTrackColor: Colors.white24),
// https://api.flutter.dev/flutter/material/TextTheme-class.html
textTheme: const TextTheme(
headline1: TextStyle(

View file

@ -21,7 +21,7 @@ String msToTimeString(Duration duration) {
final int hours = duration.inHours;
final int minutes = duration.inMinutes.remainder(60);
final String twoDigitMinutes = twoDigits(minutes);
final String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
@ -30,3 +30,8 @@ String msToTimeString(Duration duration) {
}
return '$minutes:$twoDigitSeconds';
}
String? validateNumber(bool enabled, String number) {
if (!enabled) return null;
return int.tryParse(number) == null ? 'Error' : null;
}

View file

@ -15,6 +15,8 @@ class SongListTile extends StatelessWidget {
this.highlight = false,
this.showAlbum = true,
this.subtitle = Subtitle.artist,
this.isBlockSkippedSongsEnabled,
this.blockSkippedSongsThreshold,
}) : super(key: key);
final Song song;
@ -23,9 +25,14 @@ class SongListTile extends StatelessWidget {
final bool highlight;
final bool showAlbum;
final Subtitle subtitle;
final bool? isBlockSkippedSongsEnabled;
final int? blockSkippedSongsThreshold;
@override
Widget build(BuildContext context) {
final isBlockEnabled = isBlockSkippedSongsEnabled ?? false;
final blockThreshold = blockSkippedSongsThreshold ?? 1000;
final Widget leading = showAlbum
? Image(
image: utils.getAlbumImage(song.albumArtPath),
@ -97,10 +104,16 @@ class SongListTile extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
if (song.blocked)
Icon(
const Icon(
Icons.remove_circle_outline,
size: 14.0,
color: Colors.white.withOpacity(0.4),
color: Colors.white38,
),
if (!song.blocked && isBlockEnabled && blockThreshold <= song.skipCount)
Icon(
Icons.skip_next_rounded,
size: 18.0,
color: Colors.red.shade900,
),
IconButton(
icon: const Icon(Icons.more_vert),

View file

@ -156,6 +156,8 @@ class PlaylistDao extends DatabaseAccessor<MoorDatabase>
excludeArtists: Value(filter.excludeArtists),
minPlayCount: Value(filter.minPlayCount),
maxPlayCount: Value(filter.maxPlayCount),
minSkipCount: Value(filter.minSkipCount),
maxSkipCount: Value(filter.maxSkipCount),
minLikeCount: Value(filter.minLikeCount),
maxLikeCount: Value(filter.maxLikeCount),
minYear: Value(filter.minYear),
@ -256,6 +258,11 @@ class PlaylistDao extends DatabaseAccessor<MoorDatabase>
if (filter.maxPlayCount != null)
query = query..where((tbl) => tbl.playCount.isSmallerOrEqualValue(filter.maxPlayCount));
if (filter.minSkipCount != null)
query = query..where((tbl) => tbl.skipCount.isBiggerOrEqualValue(filter.minSkipCount));
if (filter.maxSkipCount != null)
query = query..where((tbl) => tbl.skipCount.isSmallerOrEqualValue(filter.maxSkipCount));
if (filter.minYear != null)
query = query..where((tbl) => tbl.year.isBiggerOrEqualValue(filter.minYear));
if (filter.maxYear != null)
@ -312,6 +319,14 @@ List<OrderingTerm Function($SongsTable)> _generateOrderingTerms(sl.OrderBy order
),
);
break;
case sl.OrderCriterion.skipCount:
orderingTerms.add(
($SongsTable t) => OrderingTerm(
expression: t.skipCount,
mode: mode,
),
);
break;
case sl.OrderCriterion.songTitle:
orderingTerms.add(
($SongsTable t) => OrderingTerm(

View file

@ -5,7 +5,7 @@ import '../settings_data_source.dart';
part 'settings_dao.g.dart';
@UseDao(tables: [LibraryFolders, SmartLists, SmartListArtists, Artists])
@UseDao(tables: [LibraryFolders, BlockSkippedSongs])
class SettingsDao extends DatabaseAccessor<MoorDatabase>
with _$SettingsDaoMixin
implements SettingsDataSource {
@ -24,4 +24,22 @@ class SettingsDao extends DatabaseAccessor<MoorDatabase>
Future<void> addLibraryFolder(String path) async {
await into(libraryFolders).insert(LibraryFoldersCompanion(path: Value(path)));
}
@override
Stream<bool> get isBlockSkippedSongsEnabled =>
(select(blockSkippedSongs)..limit(1)).watchSingle().map((tbl) => tbl.enabled);
@override
Stream<int> get blockSkippedSongsThreshold =>
(select(blockSkippedSongs)..limit(1)).watchSingle().map((tbl) => tbl.threshold);
@override
Future<void> setBlockSkippedSongsThreshold(int threshold) async {
await update(blockSkippedSongs).write(BlockSkippedSongsCompanion(threshold: Value(threshold)));
}
@override
Future<void> setBlockSkippedSongs(bool enabled) async {
await update(blockSkippedSongs).write(BlockSkippedSongsCompanion(enabled: Value(enabled)));
}
}

View file

@ -8,8 +8,6 @@ part of 'settings_dao.dart';
mixin _$SettingsDaoMixin on DatabaseAccessor<MoorDatabase> {
$LibraryFoldersTable get libraryFolders => attachedDatabase.libraryFolders;
$SmartListsTable get smartLists => attachedDatabase.smartLists;
$SmartListArtistsTable get smartListArtists =>
attachedDatabase.smartListArtists;
$ArtistsTable get artists => attachedDatabase.artists;
$BlockSkippedSongsTable get blockSkippedSongs =>
attachedDatabase.blockSkippedSongs;
}

View file

@ -107,6 +107,11 @@ class LibraryFolders extends Table {
TextColumn get path => text()();
}
class BlockSkippedSongs extends Table {
BoolColumn get enabled => boolean().withDefault(const Constant(false))();
IntColumn get threshold => integer().withDefault(const Constant(3))();
}
class MoorAlbumOfDay extends Table {
IntColumn get albumId => integer()();
IntColumn get milliSecSinceEpoch => integer()();
@ -128,6 +133,8 @@ class SmartLists extends Table {
IntColumn get maxLikeCount => integer().withDefault(const Constant(5))();
IntColumn get minPlayCount => integer().nullable()();
IntColumn get maxPlayCount => integer().nullable()();
IntColumn get minSkipCount => integer().nullable()();
IntColumn get maxSkipCount => integer().nullable()();
IntColumn get minYear => integer().nullable()();
IntColumn get maxYear => integer().nullable()();
IntColumn get limit => integer().nullable()();
@ -173,6 +180,7 @@ class PlaylistEntries extends Table {
SmartListArtists,
Playlists,
PlaylistEntries,
BlockSkippedSongs,
],
daos: [
PersistentStateDao,
@ -192,7 +200,7 @@ class MoorDatabase extends _$MoorDatabase {
MoorDatabase.connect(DatabaseConnection connection) : super.connect(connection);
@override
int get schemaVersion => 4;
int get schemaVersion => 5;
@override
MigrationStrategy get migration => MigrationStrategy(beforeOpen: (details) async {
@ -235,6 +243,12 @@ class MoorDatabase extends _$MoorDatabase {
await m.createTable(playlists);
await m.createTable(playlistEntries);
}
if (from < 5) {
await m.addColumn(smartLists, smartLists.minSkipCount);
await m.addColumn(smartLists, smartLists.maxSkipCount);
await m.createTable(blockSkippedSongs);
await into(blockSkippedSongs).insert(const BlockSkippedSongsCompanion());
}
});
}

View file

@ -2814,6 +2814,8 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
final int maxLikeCount;
final int? minPlayCount;
final int? maxPlayCount;
final int? minSkipCount;
final int? maxSkipCount;
final int? minYear;
final int? maxYear;
final int? limit;
@ -2829,6 +2831,8 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
required this.maxLikeCount,
this.minPlayCount,
this.maxPlayCount,
this.minSkipCount,
this.maxSkipCount,
this.minYear,
this.maxYear,
this.limit,
@ -2857,6 +2861,10 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
.mapFromDatabaseResponse(data['${effectivePrefix}min_play_count']),
maxPlayCount: const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}max_play_count']),
minSkipCount: const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}min_skip_count']),
maxSkipCount: const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}max_skip_count']),
minYear: const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}min_year']),
maxYear: const IntType()
@ -2887,6 +2895,12 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
if (!nullToAbsent || maxPlayCount != null) {
map['max_play_count'] = Variable<int?>(maxPlayCount);
}
if (!nullToAbsent || minSkipCount != null) {
map['min_skip_count'] = Variable<int?>(minSkipCount);
}
if (!nullToAbsent || maxSkipCount != null) {
map['max_skip_count'] = Variable<int?>(maxSkipCount);
}
if (!nullToAbsent || minYear != null) {
map['min_year'] = Variable<int?>(minYear);
}
@ -2918,6 +2932,12 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
maxPlayCount: maxPlayCount == null && nullToAbsent
? const Value.absent()
: Value(maxPlayCount),
minSkipCount: minSkipCount == null && nullToAbsent
? const Value.absent()
: Value(minSkipCount),
maxSkipCount: maxSkipCount == null && nullToAbsent
? const Value.absent()
: Value(maxSkipCount),
minYear: minYear == null && nullToAbsent
? const Value.absent()
: Value(minYear),
@ -2944,6 +2964,8 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
maxLikeCount: serializer.fromJson<int>(json['maxLikeCount']),
minPlayCount: serializer.fromJson<int?>(json['minPlayCount']),
maxPlayCount: serializer.fromJson<int?>(json['maxPlayCount']),
minSkipCount: serializer.fromJson<int?>(json['minSkipCount']),
maxSkipCount: serializer.fromJson<int?>(json['maxSkipCount']),
minYear: serializer.fromJson<int?>(json['minYear']),
maxYear: serializer.fromJson<int?>(json['maxYear']),
limit: serializer.fromJson<int?>(json['limit']),
@ -2964,6 +2986,8 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
'maxLikeCount': serializer.toJson<int>(maxLikeCount),
'minPlayCount': serializer.toJson<int?>(minPlayCount),
'maxPlayCount': serializer.toJson<int?>(maxPlayCount),
'minSkipCount': serializer.toJson<int?>(minSkipCount),
'maxSkipCount': serializer.toJson<int?>(maxSkipCount),
'minYear': serializer.toJson<int?>(minYear),
'maxYear': serializer.toJson<int?>(maxYear),
'limit': serializer.toJson<int?>(limit),
@ -2982,6 +3006,8 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
int? maxLikeCount,
int? minPlayCount,
int? maxPlayCount,
int? minSkipCount,
int? maxSkipCount,
int? minYear,
int? maxYear,
int? limit,
@ -2997,6 +3023,8 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
maxLikeCount: maxLikeCount ?? this.maxLikeCount,
minPlayCount: minPlayCount ?? this.minPlayCount,
maxPlayCount: maxPlayCount ?? this.maxPlayCount,
minSkipCount: minSkipCount ?? this.minSkipCount,
maxSkipCount: maxSkipCount ?? this.maxSkipCount,
minYear: minYear ?? this.minYear,
maxYear: maxYear ?? this.maxYear,
limit: limit ?? this.limit,
@ -3015,6 +3043,8 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
..write('maxLikeCount: $maxLikeCount, ')
..write('minPlayCount: $minPlayCount, ')
..write('maxPlayCount: $maxPlayCount, ')
..write('minSkipCount: $minSkipCount, ')
..write('maxSkipCount: $maxSkipCount, ')
..write('minYear: $minYear, ')
..write('maxYear: $maxYear, ')
..write('limit: $limit, ')
@ -3044,15 +3074,20 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
$mrjc(
maxPlayCount.hashCode,
$mrjc(
minYear.hashCode,
minSkipCount.hashCode,
$mrjc(
maxYear.hashCode,
maxSkipCount.hashCode,
$mrjc(
limit.hashCode,
minYear.hashCode,
$mrjc(
orderCriteria.hashCode,
orderDirections
.hashCode))))))))))))));
maxYear.hashCode,
$mrjc(
limit.hashCode,
$mrjc(
orderCriteria
.hashCode,
orderDirections
.hashCode))))))))))))))));
@override
bool operator ==(Object other) =>
identical(this, other) ||
@ -3066,6 +3101,8 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
other.maxLikeCount == this.maxLikeCount &&
other.minPlayCount == this.minPlayCount &&
other.maxPlayCount == this.maxPlayCount &&
other.minSkipCount == this.minSkipCount &&
other.maxSkipCount == this.maxSkipCount &&
other.minYear == this.minYear &&
other.maxYear == this.maxYear &&
other.limit == this.limit &&
@ -3083,6 +3120,8 @@ class SmartListsCompanion extends UpdateCompanion<MoorSmartList> {
final Value<int> maxLikeCount;
final Value<int?> minPlayCount;
final Value<int?> maxPlayCount;
final Value<int?> minSkipCount;
final Value<int?> maxSkipCount;
final Value<int?> minYear;
final Value<int?> maxYear;
final Value<int?> limit;
@ -3098,6 +3137,8 @@ class SmartListsCompanion extends UpdateCompanion<MoorSmartList> {
this.maxLikeCount = const Value.absent(),
this.minPlayCount = const Value.absent(),
this.maxPlayCount = const Value.absent(),
this.minSkipCount = const Value.absent(),
this.maxSkipCount = const Value.absent(),
this.minYear = const Value.absent(),
this.maxYear = const Value.absent(),
this.limit = const Value.absent(),
@ -3114,6 +3155,8 @@ class SmartListsCompanion extends UpdateCompanion<MoorSmartList> {
this.maxLikeCount = const Value.absent(),
this.minPlayCount = const Value.absent(),
this.maxPlayCount = const Value.absent(),
this.minSkipCount = const Value.absent(),
this.maxSkipCount = const Value.absent(),
this.minYear = const Value.absent(),
this.maxYear = const Value.absent(),
this.limit = const Value.absent(),
@ -3132,6 +3175,8 @@ class SmartListsCompanion extends UpdateCompanion<MoorSmartList> {
Expression<int>? maxLikeCount,
Expression<int?>? minPlayCount,
Expression<int?>? maxPlayCount,
Expression<int?>? minSkipCount,
Expression<int?>? maxSkipCount,
Expression<int?>? minYear,
Expression<int?>? maxYear,
Expression<int?>? limit,
@ -3148,6 +3193,8 @@ class SmartListsCompanion extends UpdateCompanion<MoorSmartList> {
if (maxLikeCount != null) 'max_like_count': maxLikeCount,
if (minPlayCount != null) 'min_play_count': minPlayCount,
if (maxPlayCount != null) 'max_play_count': maxPlayCount,
if (minSkipCount != null) 'min_skip_count': minSkipCount,
if (maxSkipCount != null) 'max_skip_count': maxSkipCount,
if (minYear != null) 'min_year': minYear,
if (maxYear != null) 'max_year': maxYear,
if (limit != null) 'limit': limit,
@ -3166,6 +3213,8 @@ class SmartListsCompanion extends UpdateCompanion<MoorSmartList> {
Value<int>? maxLikeCount,
Value<int?>? minPlayCount,
Value<int?>? maxPlayCount,
Value<int?>? minSkipCount,
Value<int?>? maxSkipCount,
Value<int?>? minYear,
Value<int?>? maxYear,
Value<int?>? limit,
@ -3181,6 +3230,8 @@ class SmartListsCompanion extends UpdateCompanion<MoorSmartList> {
maxLikeCount: maxLikeCount ?? this.maxLikeCount,
minPlayCount: minPlayCount ?? this.minPlayCount,
maxPlayCount: maxPlayCount ?? this.maxPlayCount,
minSkipCount: minSkipCount ?? this.minSkipCount,
maxSkipCount: maxSkipCount ?? this.maxSkipCount,
minYear: minYear ?? this.minYear,
maxYear: maxYear ?? this.maxYear,
limit: limit ?? this.limit,
@ -3219,6 +3270,12 @@ class SmartListsCompanion extends UpdateCompanion<MoorSmartList> {
if (maxPlayCount.present) {
map['max_play_count'] = Variable<int?>(maxPlayCount.value);
}
if (minSkipCount.present) {
map['min_skip_count'] = Variable<int?>(minSkipCount.value);
}
if (maxSkipCount.present) {
map['max_skip_count'] = Variable<int?>(maxSkipCount.value);
}
if (minYear.present) {
map['min_year'] = Variable<int?>(minYear.value);
}
@ -3249,6 +3306,8 @@ class SmartListsCompanion extends UpdateCompanion<MoorSmartList> {
..write('maxLikeCount: $maxLikeCount, ')
..write('minPlayCount: $minPlayCount, ')
..write('maxPlayCount: $maxPlayCount, ')
..write('minSkipCount: $minSkipCount, ')
..write('maxSkipCount: $maxSkipCount, ')
..write('minYear: $minYear, ')
..write('maxYear: $maxYear, ')
..write('limit: $limit, ')
@ -3355,6 +3414,30 @@ class $SmartListsTable extends SmartLists
);
}
final VerificationMeta _minSkipCountMeta =
const VerificationMeta('minSkipCount');
@override
late final GeneratedIntColumn minSkipCount = _constructMinSkipCount();
GeneratedIntColumn _constructMinSkipCount() {
return GeneratedIntColumn(
'min_skip_count',
$tableName,
true,
);
}
final VerificationMeta _maxSkipCountMeta =
const VerificationMeta('maxSkipCount');
@override
late final GeneratedIntColumn maxSkipCount = _constructMaxSkipCount();
GeneratedIntColumn _constructMaxSkipCount() {
return GeneratedIntColumn(
'max_skip_count',
$tableName,
true,
);
}
final VerificationMeta _minYearMeta = const VerificationMeta('minYear');
@override
late final GeneratedIntColumn minYear = _constructMinYear();
@ -3423,6 +3506,8 @@ class $SmartListsTable extends SmartLists
maxLikeCount,
minPlayCount,
maxPlayCount,
minSkipCount,
maxSkipCount,
minYear,
maxYear,
limit,
@ -3491,6 +3576,18 @@ class $SmartListsTable extends SmartLists
maxPlayCount.isAcceptableOrUnknown(
data['max_play_count']!, _maxPlayCountMeta));
}
if (data.containsKey('min_skip_count')) {
context.handle(
_minSkipCountMeta,
minSkipCount.isAcceptableOrUnknown(
data['min_skip_count']!, _minSkipCountMeta));
}
if (data.containsKey('max_skip_count')) {
context.handle(
_maxSkipCountMeta,
maxSkipCount.isAcceptableOrUnknown(
data['max_skip_count']!, _maxSkipCountMeta));
}
if (data.containsKey('min_year')) {
context.handle(_minYearMeta,
minYear.isAcceptableOrUnknown(data['min_year']!, _minYearMeta));
@ -4153,6 +4250,188 @@ class $PlaylistEntriesTable extends PlaylistEntries
}
}
class BlockSkippedSong extends DataClass
implements Insertable<BlockSkippedSong> {
final bool enabled;
final int threshold;
BlockSkippedSong({required this.enabled, required this.threshold});
factory BlockSkippedSong.fromData(
Map<String, dynamic> data, GeneratedDatabase db,
{String? prefix}) {
final effectivePrefix = prefix ?? '';
return BlockSkippedSong(
enabled: const BoolType()
.mapFromDatabaseResponse(data['${effectivePrefix}enabled'])!,
threshold: const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}threshold'])!,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['enabled'] = Variable<bool>(enabled);
map['threshold'] = Variable<int>(threshold);
return map;
}
BlockSkippedSongsCompanion toCompanion(bool nullToAbsent) {
return BlockSkippedSongsCompanion(
enabled: Value(enabled),
threshold: Value(threshold),
);
}
factory BlockSkippedSong.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= moorRuntimeOptions.defaultSerializer;
return BlockSkippedSong(
enabled: serializer.fromJson<bool>(json['enabled']),
threshold: serializer.fromJson<int>(json['threshold']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= moorRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'enabled': serializer.toJson<bool>(enabled),
'threshold': serializer.toJson<int>(threshold),
};
}
BlockSkippedSong copyWith({bool? enabled, int? threshold}) =>
BlockSkippedSong(
enabled: enabled ?? this.enabled,
threshold: threshold ?? this.threshold,
);
@override
String toString() {
return (StringBuffer('BlockSkippedSong(')
..write('enabled: $enabled, ')
..write('threshold: $threshold')
..write(')'))
.toString();
}
@override
int get hashCode => $mrjf($mrjc(enabled.hashCode, threshold.hashCode));
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is BlockSkippedSong &&
other.enabled == this.enabled &&
other.threshold == this.threshold);
}
class BlockSkippedSongsCompanion extends UpdateCompanion<BlockSkippedSong> {
final Value<bool> enabled;
final Value<int> threshold;
const BlockSkippedSongsCompanion({
this.enabled = const Value.absent(),
this.threshold = const Value.absent(),
});
BlockSkippedSongsCompanion.insert({
this.enabled = const Value.absent(),
this.threshold = const Value.absent(),
});
static Insertable<BlockSkippedSong> custom({
Expression<bool>? enabled,
Expression<int>? threshold,
}) {
return RawValuesInsertable({
if (enabled != null) 'enabled': enabled,
if (threshold != null) 'threshold': threshold,
});
}
BlockSkippedSongsCompanion copyWith(
{Value<bool>? enabled, Value<int>? threshold}) {
return BlockSkippedSongsCompanion(
enabled: enabled ?? this.enabled,
threshold: threshold ?? this.threshold,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (enabled.present) {
map['enabled'] = Variable<bool>(enabled.value);
}
if (threshold.present) {
map['threshold'] = Variable<int>(threshold.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('BlockSkippedSongsCompanion(')
..write('enabled: $enabled, ')
..write('threshold: $threshold')
..write(')'))
.toString();
}
}
class $BlockSkippedSongsTable extends BlockSkippedSongs
with TableInfo<$BlockSkippedSongsTable, BlockSkippedSong> {
final GeneratedDatabase _db;
final String? _alias;
$BlockSkippedSongsTable(this._db, [this._alias]);
final VerificationMeta _enabledMeta = const VerificationMeta('enabled');
@override
late final GeneratedBoolColumn enabled = _constructEnabled();
GeneratedBoolColumn _constructEnabled() {
return GeneratedBoolColumn('enabled', $tableName, false,
defaultValue: const Constant(false));
}
final VerificationMeta _thresholdMeta = const VerificationMeta('threshold');
@override
late final GeneratedIntColumn threshold = _constructThreshold();
GeneratedIntColumn _constructThreshold() {
return GeneratedIntColumn('threshold', $tableName, false,
defaultValue: const Constant(3));
}
@override
List<GeneratedColumn> get $columns => [enabled, threshold];
@override
$BlockSkippedSongsTable get asDslTable => this;
@override
String get $tableName => _alias ?? 'block_skipped_songs';
@override
final String actualTableName = 'block_skipped_songs';
@override
VerificationContext validateIntegrity(Insertable<BlockSkippedSong> instance,
{bool isInserting = false}) {
final context = VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('enabled')) {
context.handle(_enabledMeta,
enabled.isAcceptableOrUnknown(data['enabled']!, _enabledMeta));
}
if (data.containsKey('threshold')) {
context.handle(_thresholdMeta,
threshold.isAcceptableOrUnknown(data['threshold']!, _thresholdMeta));
}
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => <GeneratedColumn>{};
@override
BlockSkippedSong map(Map<String, dynamic> data, {String? tablePrefix}) {
return BlockSkippedSong.fromData(data, _db,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
}
@override
$BlockSkippedSongsTable createAlias(String alias) {
return $BlockSkippedSongsTable(_db, alias);
}
}
abstract class _$MoorDatabase extends GeneratedDatabase {
_$MoorDatabase(QueryExecutor e) : super(SqlTypeSystem.defaultInstance, e);
_$MoorDatabase.connect(DatabaseConnection c) : super.connect(c);
@ -4178,6 +4457,8 @@ abstract class _$MoorDatabase extends GeneratedDatabase {
late final $PlaylistsTable playlists = $PlaylistsTable(this);
late final $PlaylistEntriesTable playlistEntries =
$PlaylistEntriesTable(this);
late final $BlockSkippedSongsTable blockSkippedSongs =
$BlockSkippedSongsTable(this);
late final PersistentStateDao persistentStateDao =
PersistentStateDao(this as MoorDatabase);
late final SettingsDao settingsDao = SettingsDao(this as MoorDatabase);
@ -4201,6 +4482,7 @@ abstract class _$MoorDatabase extends GeneratedDatabase {
smartLists,
smartListArtists,
playlists,
playlistEntries
playlistEntries,
blockSkippedSongs
];
}

View file

@ -2,4 +2,9 @@ abstract class SettingsDataSource {
Stream<List<String>> get libraryFoldersStream;
Future<void> addLibraryFolder(String path);
Future<void> removeLibraryFolder(String path);
Future<void> setBlockSkippedSongs(bool enabled);
Stream<bool> get isBlockSkippedSongsEnabled;
Future<void> setBlockSkippedSongsThreshold(int threshold);
Stream<int> get blockSkippedSongsThreshold;
}

View file

@ -28,6 +28,8 @@ class SmartListModel extends SmartList {
maxLikeCount: moorSmartList.maxLikeCount,
minPlayCount: moorSmartList.minPlayCount,
maxPlayCount: moorSmartList.maxPlayCount,
minSkipCount: moorSmartList.minSkipCount,
maxSkipCount: moorSmartList.maxSkipCount,
minYear: moorSmartList.minYear,
maxYear: moorSmartList.maxYear,
excludeBlocked: moorSmartList.excludeBlocked,
@ -57,6 +59,8 @@ class SmartListModel extends SmartList {
maxPlayCount: m.Value(filter.maxPlayCount),
minLikeCount: m.Value(filter.minLikeCount),
maxLikeCount: m.Value(filter.maxLikeCount),
minSkipCount: m.Value(filter.minSkipCount),
maxSkipCount: m.Value(filter.maxSkipCount),
minYear: m.Value(filter.minYear),
maxYear: m.Value(filter.maxYear),
excludeBlocked: m.Value(filter.excludeBlocked),

View file

@ -20,4 +20,20 @@ class SettingsRepositoryImpl implements SettingsRepository {
if (path == null) return;
await _settingsDataSource.removeLibraryFolder(path);
}
@override
Stream<int> get blockSkippedSongsThreshold => _settingsDataSource.blockSkippedSongsThreshold;
@override
Stream<bool> get isBlockSkippedSongsEnabled => _settingsDataSource.isBlockSkippedSongsEnabled;
@override
Future<void> setBlockSkippedSongsThreshold(int threshold) async {
_settingsDataSource.setBlockSkippedSongsThreshold(threshold);
}
@override
Future<void> setBlockSkippedSongs(bool enabled) async {
_settingsDataSource.setBlockSkippedSongs(enabled);
}
}