options for home widgets

This commit is contained in:
Moritz Weber 2022-09-25 10:57:07 +02:00
parent ee89a92a73
commit 528306515c
39 changed files with 1739 additions and 184 deletions

View file

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import '../../presentation/gradients.dart';
import '../../presentation/icons.dart';
import 'shuffle_mode.dart';
class CustomList {
const CustomList(
this.name,
this.iconString,
this.gradientString,
this.shuffleMode,
this.timeCreated,
this.timeChanged,
this.timeLastPlayed,
);
final String name;
final String iconString;
final String gradientString;
final ShuffleMode? shuffleMode;
final DateTime timeCreated;
final DateTime timeChanged;
final DateTime timeLastPlayed;
IconData get icon => CUSTOM_ICONS[iconString]!;
Gradient get gradient => CUSTOM_GRADIENTS[gradientString]!;
}

View file

@ -0,0 +1,17 @@
enum OrderDirection {
ascending,
descending,
}
extension OrderDirectionExtension on String {
OrderDirection? toOrderDirection() {
switch (this) {
case 'OrderDirection.ascending':
return OrderDirection.ascending;
case 'OrderDirection.descending':
return OrderDirection.descending;
default:
return null;
}
}
}

View file

@ -2,7 +2,7 @@ import '../shuffle_mode.dart';
import 'home_widget.dart';
class HomeArtistOfDay implements HomeWidget {
HomeArtistOfDay(this.position, this.shuffleMode);
HomeArtistOfDay({required this.position, required this.shuffleMode});
@override
HomeWidgetType get type => HomeWidgetType.artist_of_day;

View file

@ -1,11 +1,74 @@
import '../enums.dart';
import 'home_widget.dart';
class HomePlaylists implements HomeWidget {
HomePlaylists(this.position);
HomePlaylists({
required this.position,
this.maxEntries = 3,
this.title = 'Your Playlists',
this.orderCriterion = HomePlaylistsOrder.name,
this.orderDirection = OrderDirection.ascending,
this.filter = HomePlaylistsFilter.both,
});
@override
HomeWidgetType get type => HomeWidgetType.playlists;
@override
int position;
final String title;
final int maxEntries;
final HomePlaylistsOrder orderCriterion;
final OrderDirection orderDirection;
final HomePlaylistsFilter filter;
}
enum HomePlaylistsOrder {
name,
creationDate,
changeDate,
history,
}
extension HomePlaylistsOrderExtension on String {
HomePlaylistsOrder? toHomePlaylistsOrder() {
switch (this) {
case 'HomePlaylistsOrder.name':
return HomePlaylistsOrder.name;
case 'HomePlaylistsOrder.creationDate':
return HomePlaylistsOrder.creationDate;
case 'HomePlaylistsOrder.changeDate':
return HomePlaylistsOrder.changeDate;
case 'HomePlaylistsOrder.history':
return HomePlaylistsOrder.history;
default:
return null;
}
}
}
enum HomePlaylistsFilter {
both,
playlists,
smartlists,
}
extension HomePlaylistsFilterExtension on String {
HomePlaylistsFilter? toHomePlaylistsFilter() {
switch (this) {
case 'HomePlaylistsFilter.both':
return HomePlaylistsFilter.both;
case 'HomePlaylistsFilter.playlists':
return HomePlaylistsFilter.playlists;
case 'HomePlaylistsFilter.smartlists':
return HomePlaylistsFilter.smartlists;
default:
return null;
}
}
}

View file

@ -2,7 +2,7 @@ import '../shuffle_mode.dart';
import 'home_widget.dart';
class HomeShuffleAll implements HomeWidget {
HomeShuffleAll(this.position, this.shuffleMode);
HomeShuffleAll({required this.position, required this.shuffleMode});
@override
HomeWidgetType get type => HomeWidgetType.shuffle_all;

View file

@ -1,5 +1,3 @@
// TODO: vielleicht soll Playable immer mit einer Songliste kommen?
// dann müsste man eventuell neue Klassen definieren, die bspw. Album/Artist beinhalten
abstract class Playable {
PlayableType get type;
}

View file

@ -1,30 +1,42 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import '../../presentation/gradients.dart';
import '../../presentation/icons.dart';
import 'custom_list.dart';
import 'playable.dart';
import 'shuffle_mode.dart';
class Playlist extends Equatable implements Playable {
class Playlist extends CustomList with EquatableMixin implements Playable {
const Playlist({
required String name,
required String iconString,
required String gradientString,
required DateTime timeCreated,
required DateTime timeChanged,
required DateTime timeLastPlayed,
ShuffleMode? shuffleMode,
required this.id,
required this.name,
required this.iconString,
required this.gradientString,
this.shuffleMode,
});
}) : super(
name,
iconString,
gradientString,
shuffleMode,
timeCreated,
timeChanged,
timeLastPlayed,
);
final int id;
final String name;
final ShuffleMode? shuffleMode;
final String iconString;
final String gradientString;
IconData get icon => CUSTOM_ICONS[iconString]!;
Gradient get gradient => CUSTOM_GRADIENTS[gradientString]!;
@override
List<Object?> get props => [id, name, iconString, gradientString, shuffleMode];
List<Object?> get props => [
id,
name,
iconString,
gradientString,
shuffleMode,
timeCreated,
timeChanged,
timeLastPlayed,
];
@override
PlayableType get type => PlayableType.playlist;

View file

@ -1,35 +1,49 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import '../../presentation/gradients.dart';
import '../../presentation/icons.dart';
import 'artist.dart';
import 'custom_list.dart';
import 'enums.dart';
import 'playable.dart';
import 'shuffle_mode.dart';
class SmartList extends Equatable implements Playable {
class SmartList extends CustomList with EquatableMixin implements Playable {
const SmartList({
required String name,
required String iconString,
required String gradientString,
required DateTime timeCreated,
required DateTime timeChanged,
required DateTime timeLastPlayed,
ShuffleMode? shuffleMode,
required this.id,
required this.name,
required this.filter,
required this.orderBy,
required this.iconString,
required this.gradientString,
this.shuffleMode,
});
}) : super(
name,
iconString,
gradientString,
shuffleMode,
timeCreated,
timeChanged,
timeLastPlayed,
);
final int id;
final String name;
final Filter filter;
final OrderBy orderBy;
final ShuffleMode? shuffleMode;
final String iconString;
final String gradientString;
IconData get icon => CUSTOM_ICONS[iconString]!;
Gradient get gradient => CUSTOM_GRADIENTS[gradientString]!;
@override
List<Object?> get props => [name, filter, orderBy, shuffleMode, icon, gradient];
List<Object?> get props => [
name,
filter,
orderBy,
shuffleMode,
iconString,
gradientString,
timeCreated,
timeChanged,
timeLastPlayed,
];
@override
PlayableType get type => PlayableType.smartlist;
@ -129,21 +143,3 @@ extension OrderCriterionExtension on String {
}
}
}
enum OrderDirection {
ascending,
descending,
}
extension OrderDirectionExtension on String {
OrderDirection? toOrderDirection() {
switch (this) {
case 'OrderDirection.ascending':
return OrderDirection.ascending;
case 'OrderDirection.descending':
return OrderDirection.descending;
default:
return null;
}
}
}

View file

@ -2,6 +2,9 @@ import 'package:rxdart/rxdart.dart';
import '../entities/album.dart';
import '../entities/artist.dart';
import '../entities/custom_list.dart';
import '../entities/enums.dart';
import '../entities/home_widgets/playlists.dart';
import '../entities/playlist.dart';
import '../entities/shuffle_mode.dart';
import '../entities/smart_list.dart';
@ -27,6 +30,13 @@ abstract class MusicDataInfoRepository {
Stream<List<SmartList>> get smartListsStream;
Stream<SmartList> getSmartListStream(int smartListId);
Stream<List<CustomList>> getCustomListsStream({
HomePlaylistsOrder orderCriterion = HomePlaylistsOrder.name,
OrderDirection orderDirection = OrderDirection.ascending,
HomePlaylistsFilter filter = HomePlaylistsFilter.both,
int? limit,
});
Stream<List<Album>> get albumStream;
Stream<List<Album>> getArtistAlbumStream(Artist artist);
Future<int?> getAlbumId(String title, String artist, int? year);

View file

@ -1,13 +1,18 @@
import '../entities/playable.dart';
import '../entities/playlist.dart';
import '../entities/smart_list.dart';
import '../entities/song.dart';
import '../repositories/audio_player_repository.dart';
import '../repositories/music_data_repository.dart';
class PlaySongs {
PlaySongs(
this._audioPlayerRepository,
this._musicDataRepository,
);
final AudioPlayerRepository _audioPlayerRepository;
final MusicDataRepository _musicDataRepository;
/// Generate and play a queue from the [songs] according to current AudioPlayer settings.
Future<void> call({
@ -24,6 +29,38 @@ class PlaySongs {
keepInitialIndex: keepInitialIndex,
);
_audioPlayerRepository.play();
if (playable.type == PlayableType.playlist) {
playable as Playlist;
_musicDataRepository.updatePlaylist(
Playlist(
id: playable.id,
gradientString: playable.gradientString,
iconString: playable.iconString,
name: playable.name,
timeChanged: playable.timeChanged,
timeCreated: playable.timeCreated,
timeLastPlayed: DateTime.now(),
shuffleMode: playable.shuffleMode,
),
);
} else if (playable.type == PlayableType.smartlist) {
playable as SmartList;
_musicDataRepository.updateSmartList(
SmartList(
id: playable.id,
gradientString: playable.gradientString,
iconString: playable.iconString,
name: playable.name,
timeChanged: playable.timeChanged,
timeCreated: playable.timeCreated,
timeLastPlayed: DateTime.now(),
filter: playable.filter,
orderBy: playable.orderBy,
shuffleMode: playable.shuffleMode,
),
);
}
}
}
}

View file

@ -8,6 +8,9 @@ import 'domain/actors/persistence_actor.dart';
import 'domain/actors/platform_integration_actor.dart';
import 'domain/entities/album.dart';
import 'domain/entities/artist.dart';
import 'domain/entities/home_widgets/artist_of_day.dart';
import 'domain/entities/home_widgets/playlists.dart';
import 'domain/entities/home_widgets/shuffle_all.dart';
import 'domain/entities/playlist.dart';
import 'domain/entities/smart_list.dart';
import 'domain/entities/song.dart';
@ -30,6 +33,9 @@ import 'presentation/state/album_page_store.dart';
import 'presentation/state/artist_page_store.dart';
import 'presentation/state/audio_store.dart';
import 'presentation/state/home_page_store.dart';
import 'presentation/state/home_widget_forms/artistofday_form_store.dart';
import 'presentation/state/home_widget_forms/playlists_form_store.dart';
import 'presentation/state/home_widget_forms/shuffleall_form_store.dart';
import 'presentation/state/music_data_store.dart';
import 'presentation/state/navigation_store.dart';
import 'presentation/state/play_list_page_store.dart';
@ -134,6 +140,24 @@ Future<void> setupGetIt() async {
musicDataInfoRepository: getIt(),
),
);
getIt.registerFactoryParam<PlaylistsFormStore, HomePlaylists, void>(
(HomePlaylists playlists, _) => PlaylistsFormStore(
homeWidgetRepository: getIt(),
homePlaylists: playlists,
),
);
getIt.registerFactoryParam<ShuffleAllFormStore, HomeShuffleAll, void>(
(HomeShuffleAll shuffleAll, _) => ShuffleAllFormStore(
homeWidgetRepository: getIt(),
homeShuffleAll: shuffleAll,
),
);
getIt.registerFactoryParam<ArtistOfDayFormStore, HomeArtistOfDay, void>(
(HomeArtistOfDay artistOfDay, _) => ArtistOfDayFormStore(
homeWidgetRepository: getIt(),
homeArtistOfDay: artistOfDay,
),
);
// use cases
getIt.registerLazySingleton<PlayAlbum>(
@ -167,6 +191,7 @@ Future<void> setupGetIt() async {
getIt.registerLazySingleton<PlaySongs>(
() => PlaySongs(
getIt(),
getIt(),
),
);
getIt.registerLazySingleton<SeekToNext>(

View file

@ -4,6 +4,7 @@ import 'package:get_it/get_it.dart';
import '../../domain/entities/home_widgets/artist_of_day.dart';
import '../../domain/entities/home_widgets/home_widget.dart';
import '../../domain/entities/home_widgets/playlists.dart';
import '../../domain/entities/home_widgets/shuffle_all.dart';
import '../state/home_page_store.dart';
import '../state/navigation_store.dart';
@ -100,6 +101,6 @@ Widget _createHomeWidget(HomeWidget homeWidget) {
),
);
case HomeWidgetType.playlists:
return const SmartLists();
return SmartLists(homePlaylists: homeWidget as HomePlaylists);
}
}

View file

@ -13,6 +13,9 @@ import '../state/home_page_store.dart';
import '../state/navigation_store.dart';
import '../theming.dart';
import '../widgets/custom_modal_bottom_sheet.dart';
import 'home_widget_forms/artistofday_form_page.dart';
import 'home_widget_forms/playlists_form_page.dart';
import 'home_widget_forms/shuffle_all_form_page.dart';
class HomeSettingsPage extends StatelessWidget {
const HomeSettingsPage({Key? key}) : super(key: key);
@ -31,6 +34,13 @@ class HomeSettingsPage extends StatelessWidget {
HomeWidgetType.shuffle_all: Icons.shuffle_rounded,
};
static const hasParameters = {
HomeWidgetType.album_of_day: false,
HomeWidgetType.artist_of_day: true,
HomeWidgetType.playlists: true,
HomeWidgetType.shuffle_all: true,
};
@override
Widget build(BuildContext context) {
final homeStore = GetIt.I<HomePageStore>();
@ -57,17 +67,6 @@ class HomeSettingsPage extends StatelessWidget {
body: Observer(
builder: (context) {
final widgetEntities = homeStore.homeWidgetsStream.value ?? <HomeWidget>[];
final List<Widget> widgets = [
const SliverPadding(
padding: EdgeInsets.only(top: 8.0),
),
];
widgets.add(
const SliverPadding(
padding: EdgeInsets.only(bottom: 8.0),
),
);
return Scrollbar(
child: CustomScrollView(
@ -75,18 +74,86 @@ class HomeSettingsPage extends StatelessWidget {
ReorderableSliverList(
delegate: ReorderableSliverChildBuilderDelegate(
(context, int index) {
return ListTile(
title: Text(titles[widgetEntities[index].type]!),
leading: Icon(icons[widgetEntities[index].type]),
trailing: IconButton(
onPressed: () => _onTapMore(context, widgetEntities[index]),
icon: const Icon(Icons.more_vert_rounded),
return Dismissible(
key: UniqueKey(),
child: ListTile(
title: Text(titles[widgetEntities[index].type]!),
leading: Icon(icons[widgetEntities[index].type]),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (hasParameters[widgetEntities[index].type]!)
IconButton(
onPressed: () {
switch (widgetEntities[index].type) {
case HomeWidgetType.shuffle_all:
navStore.push(
context,
MaterialPageRoute(
builder: (context) => ShuffleAllFormPage(
shuffleAll: widgetEntities[index] as HomeShuffleAll,
),
),
);
break;
case HomeWidgetType.artist_of_day:
navStore.push(
context,
MaterialPageRoute(
builder: (context) => ArtistOfDayFormPage(
artistOfDay:
widgetEntities[index] as HomeArtistOfDay,
),
),
);
break;
case HomeWidgetType.playlists:
navStore.push(
context,
MaterialPageRoute(
builder: (context) => PlaylistsFormPage(
playlists: widgetEntities[index] as HomePlaylists,
),
),
);
break;
default:
break;
}
},
icon: const Icon(Icons.edit_rounded),
),
],
),
contentPadding: const EdgeInsets.fromLTRB(
HORIZONTAL_PADDING,
8.0,
0.0,
8.0,
),
),
contentPadding: const EdgeInsets.fromLTRB(
HORIZONTAL_PADDING,
8.0,
0.0,
8.0,
onDismissed: (direction) {
homeStore.removeHomeWidget(widgetEntities[index]);
},
background: Container(
width: double.infinity,
color: RED,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: HORIZONTAL_PADDING),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Icon(
Icons.delete_forever_rounded,
color: Colors.white,
),
Icon(
Icons.delete_forever_rounded,
color: Colors.white,
)
],
),
),
),
);
},
@ -141,54 +208,16 @@ class HomeSettingsPage extends StatelessWidget {
);
}
Future<void> _onTapMore(BuildContext context, HomeWidget homeWidget) async {
final homeStore = GetIt.I<HomePageStore>();
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => Observer(builder: (context) {
return MyBottomSheet(
widgets: [
ListTile(
title: Text(titles[homeWidget.type]!),
subtitle: Text(
'Position: ${homeWidget.position + 1}',
style: TEXT_SMALL_SUBTITLE,
),
leading: Icon(icons[homeWidget.type]),
tileColor: DARK2,
),
ListTile(
title: const Text('Remove widget'),
leading: const Icon(
Icons.delete_forever_rounded,
color: RED,
),
onTap: () {
homeStore.removeHomeWidget(homeWidget);
Navigator.of(context).pop();
},
),
],
);
}),
);
}
// TODO: replace this with opening a custom bottom sheet for components with parameters
HomeWidget _createHomeWidget(HomeWidgetType type, int position) {
switch (type) {
case HomeWidgetType.shuffle_all:
return HomeShuffleAll(position, ShuffleMode.plus);
return HomeShuffleAll(position: position, shuffleMode: ShuffleMode.plus);
case HomeWidgetType.album_of_day:
return HomeAlbumOfDay(position);
case HomeWidgetType.artist_of_day:
return HomeArtistOfDay(position, ShuffleMode.plus);
return HomeArtistOfDay(position: position, shuffleMode: ShuffleMode.plus);
case HomeWidgetType.playlists:
return HomePlaylists(position);
return HomePlaylists(position: position);
}
}
}

View file

@ -0,0 +1,134 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:get_it/get_it.dart';
import '../../../domain/entities/home_widgets/artist_of_day.dart';
import '../../../domain/entities/shuffle_mode.dart';
import '../../state/home_widget_forms/artistofday_form_store.dart';
import '../../state/navigation_store.dart';
import '../../theming.dart';
class ArtistOfDayFormPage extends StatefulWidget {
const ArtistOfDayFormPage({Key? key, required this.artistOfDay}) : super(key: key);
final HomeArtistOfDay artistOfDay;
@override
_ArtistOfDayFormPageState createState() => _ArtistOfDayFormPageState();
}
class _ArtistOfDayFormPageState extends State<ArtistOfDayFormPage> {
late ArtistOfDayFormStore store;
static const CARD_PADDING = 8.0;
@override
void initState() {
store = GetIt.I<ArtistOfDayFormStore>(param1: widget.artistOfDay);
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
final NavigationStore navStore = GetIt.I<NavigationStore>();
const playbackModeTexts = <String>[
'Normal Mode',
'Shuffle Mode',
'Favorite Shuffle Mode',
];
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: const Text(
'Edit Artist of the Day Widget',
style: TEXT_HEADER,
),
leading: IconButton(
icon: const Icon(Icons.close_rounded),
onPressed: () => navStore.pop(context),
),
actions: [
IconButton(
icon: const Icon(Icons.delete_rounded),
onPressed: () async {
// TODO: this works, but may only pop back to the smart list page...
// can I use pop 2x here?
// await musicDataStore.removePlaylist(widget.playlist!);
navStore.pop(context);
},
),
IconButton(
icon: const Icon(Icons.check_rounded),
onPressed: () async {
// store.validateAll();
// if (!store.error.hasErrors) {
await store.save();
navStore.pop(context);
// }
},
),
],
titleSpacing: 0.0,
),
body: ListTileTheme(
contentPadding: const EdgeInsets.symmetric(horizontal: HORIZONTAL_PADDING),
child: Scrollbar(
child: CustomScrollView(
slivers: [
SliverList(
delegate: SliverChildListDelegate(
[
const SizedBox(height: 16.0),
const ListTile(
title: Text('Playback Settings', style: TEXT_HEADER),
),
Card(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: CARD_PADDING,
),
child: Observer(
builder: (_) {
return Column(
children: <int>[0, 1, 2].map<RadioListTile<int>>(
(int value) {
return RadioListTile<int>(
title: Text(
playbackModeTexts[value],
style: const TextStyle(
fontSize: 14.0,
),
),
value: value,
groupValue: store.shuffleMode.index,
onChanged: (int? newValue) {
setState(() {
if (newValue != null) store.shuffleMode = ShuffleMode.values[newValue];
});
},
);
},
).toList(),
);
},
),
),
),
],
),
),
],
),
),
),
),
);
}
}

View file

@ -0,0 +1,261 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:get_it/get_it.dart';
import '../../../domain/entities/enums.dart';
import '../../../domain/entities/home_widgets/playlists.dart';
import '../../state/home_widget_forms/playlists_form_store.dart';
import '../../state/navigation_store.dart';
import '../../theming.dart';
import '../../widgets/switch_text_listtile.dart';
class PlaylistsFormPage extends StatefulWidget {
const PlaylistsFormPage({Key? key, required this.playlists}) : super(key: key);
final HomePlaylists playlists;
@override
_PlaylistsFormPageState createState() => _PlaylistsFormPageState();
}
class _PlaylistsFormPageState extends State<PlaylistsFormPage> {
late PlaylistsFormStore store;
static const CARD_PADDING = 8.0;
@override
void initState() {
store = GetIt.I<PlaylistsFormStore>(param1: widget.playlists);
super.initState();
// store.setupValidations();
}
@override
void dispose() {
// store.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final NavigationStore navStore = GetIt.I<NavigationStore>();
const orderCriterionTexts = <String>[
'Name',
'Creation Date',
'Change Date',
'Last Time Played',
];
const orderDirectionTexts = <String>[
'Ascending',
'Descending',
];
const filterTexts = <String>[
'Both',
'Playlists Only',
'Smart Lists Only',
];
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: const Text(
'Edit Playlists Widget',
style: TEXT_HEADER,
),
leading: IconButton(
icon: const Icon(Icons.close_rounded),
onPressed: () => navStore.pop(context),
),
actions: [
IconButton(
icon: const Icon(Icons.delete_rounded),
onPressed: () async {
// TODO: this works, but may only pop back to the smart list page...
// can I use pop 2x here?
// await musicDataStore.removePlaylist(widget.playlist!);
navStore.pop(context);
},
),
IconButton(
icon: const Icon(Icons.check_rounded),
onPressed: () async {
// store.validateAll();
// if (!store.error.hasErrors) {
await store.save();
navStore.pop(context);
// }
},
),
],
titleSpacing: 0.0,
),
body: ListTileTheme(
contentPadding: const EdgeInsets.symmetric(horizontal: HORIZONTAL_PADDING),
child: Scrollbar(
child: CustomScrollView(
slivers: [
SliverList(
delegate: SliverChildListDelegate(
[
const SizedBox(height: 16.0),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: Observer(
builder: (_) => TextFormField(
initialValue: store.title,
onChanged: (value) => store.title = value,
style: TEXT_HEADER,
decoration: InputDecoration(
labelText: 'Name',
labelStyle: const TextStyle(color: Colors.white),
floatingLabelStyle: TEXT_HEADER_S.copyWith(color: Colors.white),
// errorText: store.error.name,
errorStyle: const TextStyle(color: RED),
filled: true,
fillColor: DARK35,
border: const OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
contentPadding: const EdgeInsets.symmetric(
vertical: 4.0,
horizontal: 12.0,
),
),
),
),
),
const SizedBox(height: 8.0),
const ListTile(
title: Text('Sorting and Filter Settings', style: TEXT_HEADER),
),
Card(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: CARD_PADDING,
),
child: Observer(
builder: (_) {
return SwitchTextListTile(
title: 'Maximum number of entries',
switchValue: store.maxEntriesEnabled,
onSwitchChanged: (bool value) {
store.maxEntriesEnabled = value;
},
textValue: store.maxEntries,
onTextChanged: (String value) {
store.maxEntries = value;
},
);
},
),
),
),
Card(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: CARD_PADDING,
),
child: Observer(
builder: (_) {
return Column(
children: <int>[0, 1, 2, 3].map<RadioListTile<int>>((int value) {
return RadioListTile<int>(
title: Text(
orderCriterionTexts[value],
style: const TextStyle(
fontSize: 14.0,
),
),
value: value,
groupValue: store.orderCriterion.index,
onChanged: (int? newValue) {
setState(() {
if (newValue != null)
store.orderCriterion =
HomePlaylistsOrder.values[newValue];
});
},
);
}).toList(),
);
},
),
),
),
Card(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: CARD_PADDING,
),
child: Observer(
builder: (_) {
return Column(
children: <int>[0, 1].map<RadioListTile<int>>((int value) {
return RadioListTile<int>(
title: Text(
orderDirectionTexts[value],
style: const TextStyle(
fontSize: 14.0,
),
),
value: value,
groupValue: store.orderDirection.index,
onChanged: (int? newValue) {
setState(() {
if (newValue != null)
store.orderDirection = OrderDirection.values[newValue];
});
},
);
}).toList(),
);
},
),
),
),
Card(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: CARD_PADDING,
),
child: Observer(
builder: (_) {
return Column(
children: <int>[0, 1, 2].map<RadioListTile<int>>((int value) {
return RadioListTile<int>(
title: Text(
filterTexts[value],
style: const TextStyle(
fontSize: 14.0,
),
),
value: value,
groupValue: store.filter.index,
onChanged: (int? newValue) {
setState(() {
if (newValue != null)
store.filter = HomePlaylistsFilter.values[newValue];
});
},
);
}).toList(),
);
},
),
),
),
],
),
),
],
),
),
),
),
);
}
}

View file

@ -0,0 +1,134 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:get_it/get_it.dart';
import '../../../domain/entities/home_widgets/shuffle_all.dart';
import '../../../domain/entities/shuffle_mode.dart';
import '../../state/home_widget_forms/shuffleall_form_store.dart';
import '../../state/navigation_store.dart';
import '../../theming.dart';
class ShuffleAllFormPage extends StatefulWidget {
const ShuffleAllFormPage({Key? key, required this.shuffleAll}) : super(key: key);
final HomeShuffleAll shuffleAll;
@override
_ShuffleAllFormPageState createState() => _ShuffleAllFormPageState();
}
class _ShuffleAllFormPageState extends State<ShuffleAllFormPage> {
late ShuffleAllFormStore store;
static const CARD_PADDING = 8.0;
@override
void initState() {
store = GetIt.I<ShuffleAllFormStore>(param1: widget.shuffleAll);
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
final NavigationStore navStore = GetIt.I<NavigationStore>();
const playbackModeTexts = <String>[
'Normal Mode',
'Shuffle Mode',
'Favorite Shuffle Mode',
];
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: const Text(
'Edit Shuffle All Widget',
style: TEXT_HEADER,
),
leading: IconButton(
icon: const Icon(Icons.close_rounded),
onPressed: () => navStore.pop(context),
),
actions: [
IconButton(
icon: const Icon(Icons.delete_rounded),
onPressed: () async {
// TODO: this works, but may only pop back to the smart list page...
// can I use pop 2x here?
// await musicDataStore.removePlaylist(widget.playlist!);
navStore.pop(context);
},
),
IconButton(
icon: const Icon(Icons.check_rounded),
onPressed: () async {
// store.validateAll();
// if (!store.error.hasErrors) {
await store.save();
navStore.pop(context);
// }
},
),
],
titleSpacing: 0.0,
),
body: ListTileTheme(
contentPadding: const EdgeInsets.symmetric(horizontal: HORIZONTAL_PADDING),
child: Scrollbar(
child: CustomScrollView(
slivers: [
SliverList(
delegate: SliverChildListDelegate(
[
const SizedBox(height: 16.0),
const ListTile(
title: Text('Playback Settings', style: TEXT_HEADER),
),
Card(
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: CARD_PADDING,
),
child: Observer(
builder: (_) {
return Column(
children: <int>[1, 2].map<RadioListTile<int>>(
(int value) {
return RadioListTile<int>(
title: Text(
playbackModeTexts[value],
style: const TextStyle(
fontSize: 14.0,
),
),
value: value,
groupValue: store.shuffleMode.index,
onChanged: (int? newValue) {
setState(() {
if (newValue != null) store.shuffleMode = ShuffleMode.values[newValue];
});
},
);
},
).toList(),
);
},
),
),
),
],
),
),
],
),
),
),
),
);
}
}

View file

@ -39,7 +39,7 @@ class _PlaylistFormPageState extends State<PlaylistFormPage> {
@override
Widget build(BuildContext context) {
final title = widget.playlist == null ? 'Create smart list' : 'Edit smart list';
final title = widget.playlist == null ? 'Create Playlist' : 'Edit Playlist';
final NavigationStore navStore = GetIt.I<NavigationStore>();
final MusicDataStore musicDataStore = GetIt.I<MusicDataStore>();

View file

@ -11,6 +11,7 @@ import '../state/audio_store.dart';
import '../state/music_data_store.dart';
import '../state/navigation_store.dart';
import '../state/play_list_page_store.dart';
import '../theming.dart';
import '../utils.dart' as utils;
import '../widgets/bottom_sheet/add_to_playlist.dart';
import '../widgets/bottom_sheet/remove_from_playlist.dart';
@ -216,6 +217,28 @@ class _PlaylistPageState extends State<PlaylistPage> {
onDismissed: (direction) {
musicDataStore.removePlaylistEntry(playlist.id, index);
},
background: Container(
width: double.infinity,
color: RED,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: HORIZONTAL_PADDING,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Icon(
Icons.playlist_remove_rounded,
color: Colors.white,
),
Icon(
Icons.playlist_remove_rounded,
color: Colors.white,
)
],
),
),
),
);
},
childCount: songs.length,

View file

@ -4,6 +4,7 @@ import 'package:get_it/get_it.dart';
import 'package:reorderables/reorderables.dart';
import '../../constants.dart';
import '../../domain/entities/enums.dart';
import '../../domain/entities/smart_list.dart';
import '../state/music_data_store.dart';
import '../state/navigation_store.dart';

View file

@ -0,0 +1,37 @@
import 'package:mobx/mobx.dart';
import '../../../domain/entities/home_widgets/artist_of_day.dart';
import '../../../domain/entities/shuffle_mode.dart';
import '../../../domain/repositories/home_widget_repository.dart';
part 'artistofday_form_store.g.dart';
class ArtistOfDayFormStore extends _ArtistOfDayFormStore with _$ArtistOfDayFormStore {
ArtistOfDayFormStore({
required HomeWidgetRepository homeWidgetRepository,
required HomeArtistOfDay homeArtistOfDay,
}) : super(homeWidgetRepository, homeArtistOfDay);
}
abstract class _ArtistOfDayFormStore with Store {
_ArtistOfDayFormStore(
this._homeWidgetRepository,
this._shuffleAll,
);
final HomeWidgetRepository _homeWidgetRepository;
final HomeArtistOfDay _shuffleAll;
@observable
late ShuffleMode shuffleMode = _shuffleAll.shuffleMode;
Future<void> save() async {
_homeWidgetRepository.updateHomeWidget(
HomeArtistOfDay(
position: _shuffleAll.position,
shuffleMode: shuffleMode,
),
);
}
}

View file

@ -0,0 +1,34 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'artistofday_form_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, no_leading_underscores_for_local_identifiers
mixin _$ArtistOfDayFormStore on _ArtistOfDayFormStore, Store {
late final _$shuffleModeAtom =
Atom(name: '_ArtistOfDayFormStore.shuffleMode', context: context);
@override
ShuffleMode get shuffleMode {
_$shuffleModeAtom.reportRead();
return super.shuffleMode;
}
@override
set shuffleMode(ShuffleMode value) {
_$shuffleModeAtom.reportWrite(value, super.shuffleMode, () {
super.shuffleMode = value;
});
}
@override
String toString() {
return '''
shuffleMode: ${shuffleMode}
''';
}
}

View file

@ -0,0 +1,66 @@
import 'package:mobx/mobx.dart';
import '../../../domain/entities/enums.dart';
import '../../../domain/entities/home_widgets/playlists.dart';
import '../../../domain/repositories/home_widget_repository.dart';
part 'playlists_form_store.g.dart';
class PlaylistsFormStore extends _PlaylistsFormStore with _$PlaylistsFormStore {
PlaylistsFormStore({
required HomeWidgetRepository homeWidgetRepository,
required HomePlaylists homePlaylists,
}) : super(homeWidgetRepository, homePlaylists);
}
abstract class _PlaylistsFormStore with Store {
_PlaylistsFormStore(
this._homeWidgetRepository,
this._playlists,
);
final HomeWidgetRepository _homeWidgetRepository;
final HomePlaylists _playlists;
@observable
late String title = _playlists.title;
@observable
late String maxEntries = _intToString(_playlists.maxEntries > 0 ? _playlists.maxEntries : 3);
@observable
late bool maxEntriesEnabled = _playlists.maxEntries > 0;
@observable
late HomePlaylistsOrder orderCriterion = _playlists.orderCriterion;
@observable
late OrderDirection orderDirection = _playlists.orderDirection;
@observable
late HomePlaylistsFilter filter = _playlists.filter;
Future<void> save() async {
int maxEntriesInt = int.parse(maxEntries);
if (!maxEntriesEnabled || maxEntriesInt < 1) {
maxEntriesInt = 0;
}
_homeWidgetRepository.updateHomeWidget(
HomePlaylists(
position: _playlists.position,
title: title,
maxEntries: maxEntriesInt,
orderCriterion: orderCriterion,
orderDirection: orderDirection,
filter: filter,
),
);
}
}
String _intToString(int? number) {
if (number == null) return '0';
return number.toString();
}

View file

@ -0,0 +1,119 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'playlists_form_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, no_leading_underscores_for_local_identifiers
mixin _$PlaylistsFormStore on _PlaylistsFormStore, Store {
late final _$titleAtom =
Atom(name: '_PlaylistsFormStore.title', context: context);
@override
String get title {
_$titleAtom.reportRead();
return super.title;
}
@override
set title(String value) {
_$titleAtom.reportWrite(value, super.title, () {
super.title = value;
});
}
late final _$maxEntriesAtom =
Atom(name: '_PlaylistsFormStore.maxEntries', context: context);
@override
String get maxEntries {
_$maxEntriesAtom.reportRead();
return super.maxEntries;
}
@override
set maxEntries(String value) {
_$maxEntriesAtom.reportWrite(value, super.maxEntries, () {
super.maxEntries = value;
});
}
late final _$maxEntriesEnabledAtom =
Atom(name: '_PlaylistsFormStore.maxEntriesEnabled', context: context);
@override
bool get maxEntriesEnabled {
_$maxEntriesEnabledAtom.reportRead();
return super.maxEntriesEnabled;
}
@override
set maxEntriesEnabled(bool value) {
_$maxEntriesEnabledAtom.reportWrite(value, super.maxEntriesEnabled, () {
super.maxEntriesEnabled = value;
});
}
late final _$orderCriterionAtom =
Atom(name: '_PlaylistsFormStore.orderCriterion', context: context);
@override
HomePlaylistsOrder get orderCriterion {
_$orderCriterionAtom.reportRead();
return super.orderCriterion;
}
@override
set orderCriterion(HomePlaylistsOrder value) {
_$orderCriterionAtom.reportWrite(value, super.orderCriterion, () {
super.orderCriterion = value;
});
}
late final _$orderDirectionAtom =
Atom(name: '_PlaylistsFormStore.orderDirection', context: context);
@override
OrderDirection get orderDirection {
_$orderDirectionAtom.reportRead();
return super.orderDirection;
}
@override
set orderDirection(OrderDirection value) {
_$orderDirectionAtom.reportWrite(value, super.orderDirection, () {
super.orderDirection = value;
});
}
late final _$filterAtom =
Atom(name: '_PlaylistsFormStore.filter', context: context);
@override
HomePlaylistsFilter get filter {
_$filterAtom.reportRead();
return super.filter;
}
@override
set filter(HomePlaylistsFilter value) {
_$filterAtom.reportWrite(value, super.filter, () {
super.filter = value;
});
}
@override
String toString() {
return '''
title: ${title},
maxEntries: ${maxEntries},
maxEntriesEnabled: ${maxEntriesEnabled},
orderCriterion: ${orderCriterion},
orderDirection: ${orderDirection},
filter: ${filter}
''';
}
}

View file

@ -0,0 +1,37 @@
import 'package:mobx/mobx.dart';
import '../../../domain/entities/home_widgets/shuffle_all.dart';
import '../../../domain/entities/shuffle_mode.dart';
import '../../../domain/repositories/home_widget_repository.dart';
part 'shuffleall_form_store.g.dart';
class ShuffleAllFormStore extends _ShuffleAllFormStore with _$ShuffleAllFormStore {
ShuffleAllFormStore({
required HomeWidgetRepository homeWidgetRepository,
required HomeShuffleAll homeShuffleAll,
}) : super(homeWidgetRepository, homeShuffleAll);
}
abstract class _ShuffleAllFormStore with Store {
_ShuffleAllFormStore(
this._homeWidgetRepository,
this._shuffleAll,
);
final HomeWidgetRepository _homeWidgetRepository;
final HomeShuffleAll _shuffleAll;
@observable
late ShuffleMode shuffleMode = _shuffleAll.shuffleMode;
Future<void> save() async {
_homeWidgetRepository.updateHomeWidget(
HomeShuffleAll(
position: _shuffleAll.position,
shuffleMode: shuffleMode,
),
);
}
}

View file

@ -0,0 +1,34 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'shuffleall_form_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, no_leading_underscores_for_local_identifiers
mixin _$ShuffleAllFormStore on _ShuffleAllFormStore, Store {
late final _$shuffleModeAtom =
Atom(name: '_ShuffleAllFormStore.shuffleMode', context: context);
@override
ShuffleMode get shuffleMode {
_$shuffleModeAtom.reportRead();
return super.shuffleMode;
}
@override
set shuffleMode(ShuffleMode value) {
_$shuffleModeAtom.reportWrite(value, super.shuffleMode, () {
super.shuffleMode = value;
});
}
@override
String toString() {
return '''
shuffleMode: ${shuffleMode}
''';
}
}

View file

@ -2,6 +2,9 @@ import 'package:mobx/mobx.dart';
import '../../domain/entities/album.dart';
import '../../domain/entities/artist.dart';
import '../../domain/entities/custom_list.dart';
import '../../domain/entities/enums.dart';
import '../../domain/entities/home_widgets/playlists.dart';
import '../../domain/entities/playlist.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../../domain/entities/smart_list.dart';
@ -61,6 +64,22 @@ abstract class _MusicDataStore with Store {
late ObservableStream<Artist?> artistOfDay =
_musicDataRepository.artistOfDayStream.asObservable();
ObservableStream<List<CustomList>> getCustomLists({
required HomePlaylistsOrder orderCriterion,
required OrderDirection orderDirection,
required HomePlaylistsFilter filter,
int? limit,
}) {
return _musicDataRepository
.getCustomListsStream(
orderCriterion: orderCriterion,
orderDirection: orderDirection,
filter: filter,
limit: limit,
)
.asObservable();
}
@action
Future<void> updateDatabase() async {
isUpdatingDatabase = true;

View file

@ -91,6 +91,9 @@ abstract class _PlaylistStore with Store {
iconString: cover.iconString,
gradientString: cover.gradientString,
shuffleMode: shuffleMode,
timeChanged: DateTime.now(),
timeCreated: _playlist!.timeCreated,
timeLastPlayed: _playlist!.timeLastPlayed,
),
);
}
@ -106,7 +109,6 @@ abstract class _FormErrorState with Store {
bool get hasErrors => name != null;
}
int _shuffleModeIndex(ShuffleMode? shuffleMode) {
if (shuffleMode == null) return 0;
switch (shuffleMode) {

View file

@ -2,6 +2,7 @@ import 'package:mobx/mobx.dart';
import '../../constants.dart';
import '../../domain/entities/artist.dart';
import '../../domain/entities/enums.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../../domain/entities/smart_list.dart';
import '../../domain/repositories/music_data_repository.dart';
@ -240,6 +241,9 @@ abstract class _SmartListStore with Store {
name: name ?? 'This needs a name',
iconString: cover.iconString,
gradientString: cover.gradientString,
timeChanged: DateTime.now(),
timeCreated: _smartList!.timeCreated,
timeLastPlayed: _smartList!.timeLastPlayed,
shuffleMode: shuffleMode,
filter: Filter(
artists: selectedArtists.toList(),

View file

@ -2,7 +2,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:get_it/get_it.dart';
import '../../domain/entities/custom_list.dart';
import '../../domain/entities/home_widgets/playlists.dart';
import '../../domain/entities/playlist.dart';
import '../../domain/entities/smart_list.dart';
import '../pages/playlist_page.dart';
import '../pages/smart_list_page.dart';
import '../state/audio_store.dart';
import '../state/music_data_store.dart';
@ -11,7 +15,9 @@ import 'play_shuffle_button.dart';
import 'playlist_cover.dart';
class SmartLists extends StatelessWidget {
const SmartLists({Key? key}) : super(key: key);
const SmartLists({Key? key, required this.homePlaylists}) : super(key: key);
final HomePlaylists homePlaylists;
@override
Widget build(BuildContext context) {
@ -19,24 +25,40 @@ class SmartLists extends StatelessWidget {
final NavigationStore navStore = GetIt.I<NavigationStore>();
final MusicDataStore musicDataStore = GetIt.I<MusicDataStore>();
final customListsStream = musicDataStore.getCustomLists(
orderCriterion: homePlaylists.orderCriterion,
orderDirection: homePlaylists.orderDirection,
filter: homePlaylists.filter,
limit: homePlaylists.maxEntries,
);
return Observer(
builder: (context) {
final smartLists = musicDataStore.smartListsStream.value ?? [];
final customLists = customListsStream.value ?? [];
return SliverList(
delegate: SliverChildBuilderDelegate(
(_, int index) {
if (index % 2 == 1) {
return const SizedBox(height: 12.0);
}
final SmartList smartList = smartLists[index ~/ 2];
final CustomList customList = customLists[index ~/ 2];
return GestureDetector(
onTap: () => navStore.pushOnLibrary(
MaterialPageRoute<Widget>(
builder: (context) => SmartListPage(smartList: smartList),
),
),
onTap: () {
if (customList is SmartList)
navStore.pushOnLibrary(
MaterialPageRoute<Widget>(
builder: (context) => SmartListPage(smartList: customList),
),
);
else if (customList is Playlist)
navStore.pushOnLibrary(
MaterialPageRoute<Widget>(
builder: (context) => PlaylistPage(playlist: customList),
),
);
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
@ -59,8 +81,8 @@ class SmartLists extends StatelessWidget {
children: [
PlaylistCover(
size: 48,
gradient: smartList.gradient,
icon: smartList.icon,
gradient: customList.gradient,
icon: customList.icon,
shadows: const [
BoxShadow(
color: Colors.black54,
@ -72,7 +94,7 @@ class SmartLists extends StatelessWidget {
const SizedBox(width: 16.0),
Expanded(
child: Text(
smartList.name,
customList.name,
style: Theme.of(context).textTheme.headline4,
maxLines: 2,
overflow: TextOverflow.ellipsis,
@ -80,8 +102,12 @@ class SmartLists extends StatelessWidget {
),
const SizedBox(width: 8.0),
PlayShuffleButton(
onPressed: () => audioStore.playSmartList(smartList),
shuffleMode: smartList.shuffleMode,
onPressed: () {
if (customList is SmartList)
audioStore.playSmartList(customList);
else if (customList is Playlist) audioStore.playPlaylist(customList);
},
shuffleMode: customList.shuffleMode,
size: 48.0,
),
],
@ -91,7 +117,7 @@ class SmartLists extends StatelessWidget {
),
);
},
childCount: smartLists.length * 2 - 1,
childCount: customLists.length * 2 - 1,
),
);
},

View file

@ -67,9 +67,9 @@ class HomeWidgetDao extends DatabaseAccessor<MoorDatabase>
}
@override
Future<void> updateHomeWidget(HomeWidgetModel homeWidget) {
// TODO: implement updateHomeWidget
throw UnimplementedError();
Future<void> updateHomeWidget(HomeWidgetModel homeWidget) async {
await (update(homeWidgets)..where((tbl) => tbl.position.equals(homeWidget.position)))
.write(homeWidget.toMoor());
}
HomeWidgetModel _getHomeWidget(MoorHomeWidget moorHomeWidget) {

View file

@ -37,6 +37,9 @@ class PlaylistDao extends DatabaseAccessor<MoorDatabase>
i++;
}
await (update(playlists)..where((tbl) => tbl.id.equals(playlist.id)))
.write(PlaylistsCompanion(timeChanged: Value(DateTime.now())));
await batch((batch) {
batch.insertAll(playlistEntries, entries);
});
@ -121,6 +124,9 @@ class PlaylistDao extends DatabaseAccessor<MoorDatabase>
await (update(playlistEntries)
..where((tbl) => tbl.position.equals(-1) & tbl.playlistId.equals(playlistId)))
.write(PlaylistEntriesCompanion(position: Value(newIndex)));
await (update(playlists)..where((tbl) => tbl.id.equals(playlistId)))
.write(PlaylistsCompanion(timeChanged: Value(DateTime.now())));
});
}
}
@ -140,6 +146,9 @@ class PlaylistDao extends DatabaseAccessor<MoorDatabase>
..where((tbl) => tbl.position.equals(i) & tbl.playlistId.equals(playlistId)))
.write(PlaylistEntriesCompanion(position: Value(i - 1)));
}
await (update(playlists)..where((tbl) => tbl.id.equals(playlistId)))
.write(PlaylistsCompanion(timeChanged: Value(DateTime.now())));
});
}

View file

@ -112,6 +112,10 @@ class SmartLists extends Table {
TextColumn get shuffleMode => text().nullable()();
TextColumn get icon => text().withDefault(const Constant('auto_awesome_rounded'))();
TextColumn get gradient => text().withDefault(const Constant('sanguine'))();
DateTimeColumn get timeCreated => dateTime().withDefault(currentDateAndTime)();
DateTimeColumn get timeChanged => dateTime().withDefault(currentDateAndTime)();
DateTimeColumn get timeLastPlayed =>
dateTime().withDefault(Constant(DateTime.fromMillisecondsSinceEpoch(0)))();
// Filter
BoolColumn get excludeArtists => boolean().withDefault(const Constant(false))();
@ -144,6 +148,10 @@ class Playlists extends Table {
TextColumn get shuffleMode => text().nullable()();
TextColumn get icon => text().withDefault(const Constant('queue_music_rounded'))();
TextColumn get gradient => text().withDefault(const Constant('oceanblue'))();
DateTimeColumn get timeCreated => dateTime().withDefault(currentDateAndTime)();
DateTimeColumn get timeChanged => dateTime().withDefault(currentDateAndTime)();
DateTimeColumn get timeLastPlayed =>
dateTime().withDefault(Constant(DateTime.fromMillisecondsSinceEpoch(0)))();
}
@DataClassName('MoorPlaylistEntry')
@ -197,7 +205,7 @@ class MoorDatabase extends _$MoorDatabase {
MoorDatabase.connect(DatabaseConnection connection) : super.connect(connection);
@override
int get schemaVersion => 8;
int get schemaVersion => 9;
@override
MigrationStrategy get migration => MigrationStrategy(
@ -244,10 +252,18 @@ class MoorDatabase extends _$MoorDatabase {
),
);
await into(homeWidgets).insert(
const HomeWidgetsCompanion(
position: Value(3),
type: Value('HomeWidgetType.playlists'),
data: Value('{}'),
HomeWidgetsCompanion(
position: const Value(3),
type: const Value('HomeWidgetType.playlists'),
data: Value(
json.encode({
'title': 'Your Playlists',
'maxEntries': 3,
'orderCriterion': 'HomePlaylistsOrder.name',
'orderDirection': 'OrderDirection.ascending',
'filter': 'HomePlaylistsFilter.both',
}),
),
),
);
}
@ -323,6 +339,19 @@ class MoorDatabase extends _$MoorDatabase {
),
);
}
if (from < 9) {
final now = DateTime.now();
await m.addColumn(smartLists, smartLists.timeLastPlayed);
await m.alterTable(TableMigration(smartLists, columnTransformer: {
smartLists.timeChanged: Constant(now),
smartLists.timeCreated: Constant(now),
}));
await m.addColumn(playlists, playlists.timeLastPlayed);
await m.alterTable(TableMigration(playlists, columnTransformer: {
playlists.timeChanged: Constant(now),
playlists.timeCreated: Constant(now),
}));
}
},
);
}

View file

@ -2024,6 +2024,9 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
final String? shuffleMode;
final String icon;
final String gradient;
final DateTime timeCreated;
final DateTime timeChanged;
final DateTime timeLastPlayed;
final bool excludeArtists;
final int blockLevel;
final int minLikeCount;
@ -2043,6 +2046,9 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
this.shuffleMode,
required this.icon,
required this.gradient,
required this.timeCreated,
required this.timeChanged,
required this.timeLastPlayed,
required this.excludeArtists,
required this.blockLevel,
required this.minLikeCount,
@ -2069,6 +2075,12 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
.mapFromDatabaseResponse(data['${effectivePrefix}icon'])!,
gradient: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}gradient'])!,
timeCreated: const DateTimeType()
.mapFromDatabaseResponse(data['${effectivePrefix}time_created'])!,
timeChanged: const DateTimeType()
.mapFromDatabaseResponse(data['${effectivePrefix}time_changed'])!,
timeLastPlayed: const DateTimeType()
.mapFromDatabaseResponse(data['${effectivePrefix}time_last_played'])!,
excludeArtists: const BoolType()
.mapFromDatabaseResponse(data['${effectivePrefix}exclude_artists'])!,
blockLevel: const IntType()
@ -2107,6 +2119,9 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
}
map['icon'] = Variable<String>(icon);
map['gradient'] = Variable<String>(gradient);
map['time_created'] = Variable<DateTime>(timeCreated);
map['time_changed'] = Variable<DateTime>(timeChanged);
map['time_last_played'] = Variable<DateTime>(timeLastPlayed);
map['exclude_artists'] = Variable<bool>(excludeArtists);
map['block_level'] = Variable<int>(blockLevel);
map['min_like_count'] = Variable<int>(minLikeCount);
@ -2146,6 +2161,9 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
: Value(shuffleMode),
icon: Value(icon),
gradient: Value(gradient),
timeCreated: Value(timeCreated),
timeChanged: Value(timeChanged),
timeLastPlayed: Value(timeLastPlayed),
excludeArtists: Value(excludeArtists),
blockLevel: Value(blockLevel),
minLikeCount: Value(minLikeCount),
@ -2184,6 +2202,9 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
shuffleMode: serializer.fromJson<String?>(json['shuffleMode']),
icon: serializer.fromJson<String>(json['icon']),
gradient: serializer.fromJson<String>(json['gradient']),
timeCreated: serializer.fromJson<DateTime>(json['timeCreated']),
timeChanged: serializer.fromJson<DateTime>(json['timeChanged']),
timeLastPlayed: serializer.fromJson<DateTime>(json['timeLastPlayed']),
excludeArtists: serializer.fromJson<bool>(json['excludeArtists']),
blockLevel: serializer.fromJson<int>(json['blockLevel']),
minLikeCount: serializer.fromJson<int>(json['minLikeCount']),
@ -2208,6 +2229,9 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
'shuffleMode': serializer.toJson<String?>(shuffleMode),
'icon': serializer.toJson<String>(icon),
'gradient': serializer.toJson<String>(gradient),
'timeCreated': serializer.toJson<DateTime>(timeCreated),
'timeChanged': serializer.toJson<DateTime>(timeChanged),
'timeLastPlayed': serializer.toJson<DateTime>(timeLastPlayed),
'excludeArtists': serializer.toJson<bool>(excludeArtists),
'blockLevel': serializer.toJson<int>(blockLevel),
'minLikeCount': serializer.toJson<int>(minLikeCount),
@ -2230,6 +2254,9 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
String? shuffleMode,
String? icon,
String? gradient,
DateTime? timeCreated,
DateTime? timeChanged,
DateTime? timeLastPlayed,
bool? excludeArtists,
int? blockLevel,
int? minLikeCount,
@ -2249,6 +2276,9 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
shuffleMode: shuffleMode ?? this.shuffleMode,
icon: icon ?? this.icon,
gradient: gradient ?? this.gradient,
timeCreated: timeCreated ?? this.timeCreated,
timeChanged: timeChanged ?? this.timeChanged,
timeLastPlayed: timeLastPlayed ?? this.timeLastPlayed,
excludeArtists: excludeArtists ?? this.excludeArtists,
blockLevel: blockLevel ?? this.blockLevel,
minLikeCount: minLikeCount ?? this.minLikeCount,
@ -2271,6 +2301,9 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
..write('shuffleMode: $shuffleMode, ')
..write('icon: $icon, ')
..write('gradient: $gradient, ')
..write('timeCreated: $timeCreated, ')
..write('timeChanged: $timeChanged, ')
..write('timeLastPlayed: $timeLastPlayed, ')
..write('excludeArtists: $excludeArtists, ')
..write('blockLevel: $blockLevel, ')
..write('minLikeCount: $minLikeCount, ')
@ -2289,25 +2322,29 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
}
@override
int get hashCode => Object.hash(
id,
name,
shuffleMode,
icon,
gradient,
excludeArtists,
blockLevel,
minLikeCount,
maxLikeCount,
minPlayCount,
maxPlayCount,
minSkipCount,
maxSkipCount,
minYear,
maxYear,
limit,
orderCriteria,
orderDirections);
int get hashCode => Object.hashAll([
id,
name,
shuffleMode,
icon,
gradient,
timeCreated,
timeChanged,
timeLastPlayed,
excludeArtists,
blockLevel,
minLikeCount,
maxLikeCount,
minPlayCount,
maxPlayCount,
minSkipCount,
maxSkipCount,
minYear,
maxYear,
limit,
orderCriteria,
orderDirections
]);
@override
bool operator ==(Object other) =>
identical(this, other) ||
@ -2317,6 +2354,9 @@ class MoorSmartList extends DataClass implements Insertable<MoorSmartList> {
other.shuffleMode == this.shuffleMode &&
other.icon == this.icon &&
other.gradient == this.gradient &&
other.timeCreated == this.timeCreated &&
other.timeChanged == this.timeChanged &&
other.timeLastPlayed == this.timeLastPlayed &&
other.excludeArtists == this.excludeArtists &&
other.blockLevel == this.blockLevel &&
other.minLikeCount == this.minLikeCount &&
@ -2338,6 +2378,9 @@ class SmartListsCompanion extends UpdateCompanion<MoorSmartList> {
final Value<String?> shuffleMode;
final Value<String> icon;
final Value<String> gradient;
final Value<DateTime> timeCreated;
final Value<DateTime> timeChanged;
final Value<DateTime> timeLastPlayed;
final Value<bool> excludeArtists;
final Value<int> blockLevel;
final Value<int> minLikeCount;
@ -2357,6 +2400,9 @@ class SmartListsCompanion extends UpdateCompanion<MoorSmartList> {
this.shuffleMode = const Value.absent(),
this.icon = const Value.absent(),
this.gradient = const Value.absent(),
this.timeCreated = const Value.absent(),
this.timeChanged = const Value.absent(),
this.timeLastPlayed = const Value.absent(),
this.excludeArtists = const Value.absent(),
this.blockLevel = const Value.absent(),
this.minLikeCount = const Value.absent(),
@ -2377,6 +2423,9 @@ class SmartListsCompanion extends UpdateCompanion<MoorSmartList> {
this.shuffleMode = const Value.absent(),
this.icon = const Value.absent(),
this.gradient = const Value.absent(),
this.timeCreated = const Value.absent(),
this.timeChanged = const Value.absent(),
this.timeLastPlayed = const Value.absent(),
this.excludeArtists = const Value.absent(),
this.blockLevel = const Value.absent(),
this.minLikeCount = const Value.absent(),
@ -2399,6 +2448,9 @@ class SmartListsCompanion extends UpdateCompanion<MoorSmartList> {
Expression<String?>? shuffleMode,
Expression<String>? icon,
Expression<String>? gradient,
Expression<DateTime>? timeCreated,
Expression<DateTime>? timeChanged,
Expression<DateTime>? timeLastPlayed,
Expression<bool>? excludeArtists,
Expression<int>? blockLevel,
Expression<int>? minLikeCount,
@ -2419,6 +2471,9 @@ class SmartListsCompanion extends UpdateCompanion<MoorSmartList> {
if (shuffleMode != null) 'shuffle_mode': shuffleMode,
if (icon != null) 'icon': icon,
if (gradient != null) 'gradient': gradient,
if (timeCreated != null) 'time_created': timeCreated,
if (timeChanged != null) 'time_changed': timeChanged,
if (timeLastPlayed != null) 'time_last_played': timeLastPlayed,
if (excludeArtists != null) 'exclude_artists': excludeArtists,
if (blockLevel != null) 'block_level': blockLevel,
if (minLikeCount != null) 'min_like_count': minLikeCount,
@ -2441,6 +2496,9 @@ class SmartListsCompanion extends UpdateCompanion<MoorSmartList> {
Value<String?>? shuffleMode,
Value<String>? icon,
Value<String>? gradient,
Value<DateTime>? timeCreated,
Value<DateTime>? timeChanged,
Value<DateTime>? timeLastPlayed,
Value<bool>? excludeArtists,
Value<int>? blockLevel,
Value<int>? minLikeCount,
@ -2460,6 +2518,9 @@ class SmartListsCompanion extends UpdateCompanion<MoorSmartList> {
shuffleMode: shuffleMode ?? this.shuffleMode,
icon: icon ?? this.icon,
gradient: gradient ?? this.gradient,
timeCreated: timeCreated ?? this.timeCreated,
timeChanged: timeChanged ?? this.timeChanged,
timeLastPlayed: timeLastPlayed ?? this.timeLastPlayed,
excludeArtists: excludeArtists ?? this.excludeArtists,
blockLevel: blockLevel ?? this.blockLevel,
minLikeCount: minLikeCount ?? this.minLikeCount,
@ -2494,6 +2555,15 @@ class SmartListsCompanion extends UpdateCompanion<MoorSmartList> {
if (gradient.present) {
map['gradient'] = Variable<String>(gradient.value);
}
if (timeCreated.present) {
map['time_created'] = Variable<DateTime>(timeCreated.value);
}
if (timeChanged.present) {
map['time_changed'] = Variable<DateTime>(timeChanged.value);
}
if (timeLastPlayed.present) {
map['time_last_played'] = Variable<DateTime>(timeLastPlayed.value);
}
if (excludeArtists.present) {
map['exclude_artists'] = Variable<bool>(excludeArtists.value);
}
@ -2544,6 +2614,9 @@ class SmartListsCompanion extends UpdateCompanion<MoorSmartList> {
..write('shuffleMode: $shuffleMode, ')
..write('icon: $icon, ')
..write('gradient: $gradient, ')
..write('timeCreated: $timeCreated, ')
..write('timeChanged: $timeChanged, ')
..write('timeLastPlayed: $timeLastPlayed, ')
..write('excludeArtists: $excludeArtists, ')
..write('blockLevel: $blockLevel, ')
..write('minLikeCount: $minLikeCount, ')
@ -2600,6 +2673,30 @@ class $SmartListsTable extends SmartLists
type: const StringType(),
requiredDuringInsert: false,
defaultValue: const Constant('sanguine'));
final VerificationMeta _timeCreatedMeta =
const VerificationMeta('timeCreated');
@override
late final GeneratedColumn<DateTime?> timeCreated =
GeneratedColumn<DateTime?>('time_created', aliasedName, false,
type: const IntType(),
requiredDuringInsert: false,
defaultValue: currentDateAndTime);
final VerificationMeta _timeChangedMeta =
const VerificationMeta('timeChanged');
@override
late final GeneratedColumn<DateTime?> timeChanged =
GeneratedColumn<DateTime?>('time_changed', aliasedName, false,
type: const IntType(),
requiredDuringInsert: false,
defaultValue: currentDateAndTime);
final VerificationMeta _timeLastPlayedMeta =
const VerificationMeta('timeLastPlayed');
@override
late final GeneratedColumn<DateTime?> timeLastPlayed =
GeneratedColumn<DateTime?>('time_last_played', aliasedName, false,
type: const IntType(),
requiredDuringInsert: false,
defaultValue: Constant(DateTime.fromMillisecondsSinceEpoch(0)));
final VerificationMeta _excludeArtistsMeta =
const VerificationMeta('excludeArtists');
@override
@ -2690,6 +2787,9 @@ class $SmartListsTable extends SmartLists
shuffleMode,
icon,
gradient,
timeCreated,
timeChanged,
timeLastPlayed,
excludeArtists,
blockLevel,
minLikeCount,
@ -2736,6 +2836,24 @@ class $SmartListsTable extends SmartLists
context.handle(_gradientMeta,
gradient.isAcceptableOrUnknown(data['gradient']!, _gradientMeta));
}
if (data.containsKey('time_created')) {
context.handle(
_timeCreatedMeta,
timeCreated.isAcceptableOrUnknown(
data['time_created']!, _timeCreatedMeta));
}
if (data.containsKey('time_changed')) {
context.handle(
_timeChangedMeta,
timeChanged.isAcceptableOrUnknown(
data['time_changed']!, _timeChangedMeta));
}
if (data.containsKey('time_last_played')) {
context.handle(
_timeLastPlayedMeta,
timeLastPlayed.isAcceptableOrUnknown(
data['time_last_played']!, _timeLastPlayedMeta));
}
if (data.containsKey('exclude_artists')) {
context.handle(
_excludeArtistsMeta,
@ -3020,12 +3138,18 @@ class MoorPlaylist extends DataClass implements Insertable<MoorPlaylist> {
final String? shuffleMode;
final String icon;
final String gradient;
final DateTime timeCreated;
final DateTime timeChanged;
final DateTime timeLastPlayed;
MoorPlaylist(
{required this.id,
required this.name,
this.shuffleMode,
required this.icon,
required this.gradient});
required this.gradient,
required this.timeCreated,
required this.timeChanged,
required this.timeLastPlayed});
factory MoorPlaylist.fromData(Map<String, dynamic> data, {String? prefix}) {
final effectivePrefix = prefix ?? '';
return MoorPlaylist(
@ -3039,6 +3163,12 @@ class MoorPlaylist extends DataClass implements Insertable<MoorPlaylist> {
.mapFromDatabaseResponse(data['${effectivePrefix}icon'])!,
gradient: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}gradient'])!,
timeCreated: const DateTimeType()
.mapFromDatabaseResponse(data['${effectivePrefix}time_created'])!,
timeChanged: const DateTimeType()
.mapFromDatabaseResponse(data['${effectivePrefix}time_changed'])!,
timeLastPlayed: const DateTimeType()
.mapFromDatabaseResponse(data['${effectivePrefix}time_last_played'])!,
);
}
@override
@ -3051,6 +3181,9 @@ class MoorPlaylist extends DataClass implements Insertable<MoorPlaylist> {
}
map['icon'] = Variable<String>(icon);
map['gradient'] = Variable<String>(gradient);
map['time_created'] = Variable<DateTime>(timeCreated);
map['time_changed'] = Variable<DateTime>(timeChanged);
map['time_last_played'] = Variable<DateTime>(timeLastPlayed);
return map;
}
@ -3063,6 +3196,9 @@ class MoorPlaylist extends DataClass implements Insertable<MoorPlaylist> {
: Value(shuffleMode),
icon: Value(icon),
gradient: Value(gradient),
timeCreated: Value(timeCreated),
timeChanged: Value(timeChanged),
timeLastPlayed: Value(timeLastPlayed),
);
}
@ -3075,6 +3211,9 @@ class MoorPlaylist extends DataClass implements Insertable<MoorPlaylist> {
shuffleMode: serializer.fromJson<String?>(json['shuffleMode']),
icon: serializer.fromJson<String>(json['icon']),
gradient: serializer.fromJson<String>(json['gradient']),
timeCreated: serializer.fromJson<DateTime>(json['timeCreated']),
timeChanged: serializer.fromJson<DateTime>(json['timeChanged']),
timeLastPlayed: serializer.fromJson<DateTime>(json['timeLastPlayed']),
);
}
@override
@ -3086,6 +3225,9 @@ class MoorPlaylist extends DataClass implements Insertable<MoorPlaylist> {
'shuffleMode': serializer.toJson<String?>(shuffleMode),
'icon': serializer.toJson<String>(icon),
'gradient': serializer.toJson<String>(gradient),
'timeCreated': serializer.toJson<DateTime>(timeCreated),
'timeChanged': serializer.toJson<DateTime>(timeChanged),
'timeLastPlayed': serializer.toJson<DateTime>(timeLastPlayed),
};
}
@ -3094,13 +3236,19 @@ class MoorPlaylist extends DataClass implements Insertable<MoorPlaylist> {
String? name,
String? shuffleMode,
String? icon,
String? gradient}) =>
String? gradient,
DateTime? timeCreated,
DateTime? timeChanged,
DateTime? timeLastPlayed}) =>
MoorPlaylist(
id: id ?? this.id,
name: name ?? this.name,
shuffleMode: shuffleMode ?? this.shuffleMode,
icon: icon ?? this.icon,
gradient: gradient ?? this.gradient,
timeCreated: timeCreated ?? this.timeCreated,
timeChanged: timeChanged ?? this.timeChanged,
timeLastPlayed: timeLastPlayed ?? this.timeLastPlayed,
);
@override
String toString() {
@ -3109,13 +3257,17 @@ class MoorPlaylist extends DataClass implements Insertable<MoorPlaylist> {
..write('name: $name, ')
..write('shuffleMode: $shuffleMode, ')
..write('icon: $icon, ')
..write('gradient: $gradient')
..write('gradient: $gradient, ')
..write('timeCreated: $timeCreated, ')
..write('timeChanged: $timeChanged, ')
..write('timeLastPlayed: $timeLastPlayed')
..write(')'))
.toString();
}
@override
int get hashCode => Object.hash(id, name, shuffleMode, icon, gradient);
int get hashCode => Object.hash(id, name, shuffleMode, icon, gradient,
timeCreated, timeChanged, timeLastPlayed);
@override
bool operator ==(Object other) =>
identical(this, other) ||
@ -3124,7 +3276,10 @@ class MoorPlaylist extends DataClass implements Insertable<MoorPlaylist> {
other.name == this.name &&
other.shuffleMode == this.shuffleMode &&
other.icon == this.icon &&
other.gradient == this.gradient);
other.gradient == this.gradient &&
other.timeCreated == this.timeCreated &&
other.timeChanged == this.timeChanged &&
other.timeLastPlayed == this.timeLastPlayed);
}
class PlaylistsCompanion extends UpdateCompanion<MoorPlaylist> {
@ -3133,12 +3288,18 @@ class PlaylistsCompanion extends UpdateCompanion<MoorPlaylist> {
final Value<String?> shuffleMode;
final Value<String> icon;
final Value<String> gradient;
final Value<DateTime> timeCreated;
final Value<DateTime> timeChanged;
final Value<DateTime> timeLastPlayed;
const PlaylistsCompanion({
this.id = const Value.absent(),
this.name = const Value.absent(),
this.shuffleMode = const Value.absent(),
this.icon = const Value.absent(),
this.gradient = const Value.absent(),
this.timeCreated = const Value.absent(),
this.timeChanged = const Value.absent(),
this.timeLastPlayed = const Value.absent(),
});
PlaylistsCompanion.insert({
this.id = const Value.absent(),
@ -3146,6 +3307,9 @@ class PlaylistsCompanion extends UpdateCompanion<MoorPlaylist> {
this.shuffleMode = const Value.absent(),
this.icon = const Value.absent(),
this.gradient = const Value.absent(),
this.timeCreated = const Value.absent(),
this.timeChanged = const Value.absent(),
this.timeLastPlayed = const Value.absent(),
}) : name = Value(name);
static Insertable<MoorPlaylist> custom({
Expression<int>? id,
@ -3153,6 +3317,9 @@ class PlaylistsCompanion extends UpdateCompanion<MoorPlaylist> {
Expression<String?>? shuffleMode,
Expression<String>? icon,
Expression<String>? gradient,
Expression<DateTime>? timeCreated,
Expression<DateTime>? timeChanged,
Expression<DateTime>? timeLastPlayed,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
@ -3160,6 +3327,9 @@ class PlaylistsCompanion extends UpdateCompanion<MoorPlaylist> {
if (shuffleMode != null) 'shuffle_mode': shuffleMode,
if (icon != null) 'icon': icon,
if (gradient != null) 'gradient': gradient,
if (timeCreated != null) 'time_created': timeCreated,
if (timeChanged != null) 'time_changed': timeChanged,
if (timeLastPlayed != null) 'time_last_played': timeLastPlayed,
});
}
@ -3168,13 +3338,19 @@ class PlaylistsCompanion extends UpdateCompanion<MoorPlaylist> {
Value<String>? name,
Value<String?>? shuffleMode,
Value<String>? icon,
Value<String>? gradient}) {
Value<String>? gradient,
Value<DateTime>? timeCreated,
Value<DateTime>? timeChanged,
Value<DateTime>? timeLastPlayed}) {
return PlaylistsCompanion(
id: id ?? this.id,
name: name ?? this.name,
shuffleMode: shuffleMode ?? this.shuffleMode,
icon: icon ?? this.icon,
gradient: gradient ?? this.gradient,
timeCreated: timeCreated ?? this.timeCreated,
timeChanged: timeChanged ?? this.timeChanged,
timeLastPlayed: timeLastPlayed ?? this.timeLastPlayed,
);
}
@ -3196,6 +3372,15 @@ class PlaylistsCompanion extends UpdateCompanion<MoorPlaylist> {
if (gradient.present) {
map['gradient'] = Variable<String>(gradient.value);
}
if (timeCreated.present) {
map['time_created'] = Variable<DateTime>(timeCreated.value);
}
if (timeChanged.present) {
map['time_changed'] = Variable<DateTime>(timeChanged.value);
}
if (timeLastPlayed.present) {
map['time_last_played'] = Variable<DateTime>(timeLastPlayed.value);
}
return map;
}
@ -3206,7 +3391,10 @@ class PlaylistsCompanion extends UpdateCompanion<MoorPlaylist> {
..write('name: $name, ')
..write('shuffleMode: $shuffleMode, ')
..write('icon: $icon, ')
..write('gradient: $gradient')
..write('gradient: $gradient, ')
..write('timeCreated: $timeCreated, ')
..write('timeChanged: $timeChanged, ')
..write('timeLastPlayed: $timeLastPlayed')
..write(')'))
.toString();
}
@ -3250,8 +3438,41 @@ class $PlaylistsTable extends Playlists
type: const StringType(),
requiredDuringInsert: false,
defaultValue: const Constant('oceanblue'));
final VerificationMeta _timeCreatedMeta =
const VerificationMeta('timeCreated');
@override
List<GeneratedColumn> get $columns => [id, name, shuffleMode, icon, gradient];
late final GeneratedColumn<DateTime?> timeCreated =
GeneratedColumn<DateTime?>('time_created', aliasedName, false,
type: const IntType(),
requiredDuringInsert: false,
defaultValue: currentDateAndTime);
final VerificationMeta _timeChangedMeta =
const VerificationMeta('timeChanged');
@override
late final GeneratedColumn<DateTime?> timeChanged =
GeneratedColumn<DateTime?>('time_changed', aliasedName, false,
type: const IntType(),
requiredDuringInsert: false,
defaultValue: currentDateAndTime);
final VerificationMeta _timeLastPlayedMeta =
const VerificationMeta('timeLastPlayed');
@override
late final GeneratedColumn<DateTime?> timeLastPlayed =
GeneratedColumn<DateTime?>('time_last_played', aliasedName, false,
type: const IntType(),
requiredDuringInsert: false,
defaultValue: Constant(DateTime.fromMillisecondsSinceEpoch(0)));
@override
List<GeneratedColumn> get $columns => [
id,
name,
shuffleMode,
icon,
gradient,
timeCreated,
timeChanged,
timeLastPlayed
];
@override
String get aliasedName => _alias ?? 'playlists';
@override
@ -3284,6 +3505,24 @@ class $PlaylistsTable extends Playlists
context.handle(_gradientMeta,
gradient.isAcceptableOrUnknown(data['gradient']!, _gradientMeta));
}
if (data.containsKey('time_created')) {
context.handle(
_timeCreatedMeta,
timeCreated.isAcceptableOrUnknown(
data['time_created']!, _timeCreatedMeta));
}
if (data.containsKey('time_changed')) {
context.handle(
_timeChangedMeta,
timeChanged.isAcceptableOrUnknown(
data['time_changed']!, _timeChangedMeta));
}
if (data.containsKey('time_last_played')) {
context.handle(
_timeLastPlayedMeta,
timeLastPlayed.isAcceptableOrUnknown(
data['time_last_played']!, _timeLastPlayedMeta));
}
return context;
}

View file

@ -9,7 +9,13 @@ import '../../datasources/moor_database.dart';
import 'home_widget_model.dart';
class HomeArtistOfDayModel extends HomeArtistOfDay implements HomeWidgetModel {
HomeArtistOfDayModel(super.position, super.shuffleMode);
HomeArtistOfDayModel(
int position,
ShuffleMode shuffleMode,
) : super(
position: position,
shuffleMode: shuffleMode,
);
factory HomeArtistOfDayModel.fromMoor(MoorHomeWidget moorHomeWidget) {
final type = moorHomeWidget.type.toHomeWidgetType();
@ -32,11 +38,11 @@ class HomeArtistOfDayModel extends HomeArtistOfDay implements HomeWidgetModel {
@override
HomeWidgetsCompanion toMoor() {
final data = '{"shuffleMode": "$shuffleMode"}';
final data = {'shuffleMode': '$shuffleMode'};
return HomeWidgetsCompanion(
position: Value(position),
type: Value(type.toString()),
data: Value(data),
data: Value(json.encode(data)),
);
}
}

View file

@ -1,12 +1,29 @@
import 'dart:convert';
import 'package:drift/drift.dart';
import '../../../domain/entities/enums.dart';
import '../../../domain/entities/home_widgets/home_widget.dart';
import '../../../domain/entities/home_widgets/playlists.dart';
import '../../datasources/moor_database.dart';
import 'home_widget_model.dart';
class HomePlaylistsModel extends HomePlaylists implements HomeWidgetModel {
HomePlaylistsModel(super.position);
HomePlaylistsModel(
int position,
int maxEntries,
String title,
HomePlaylistsOrder orderCriterion,
OrderDirection orderDirection,
HomePlaylistsFilter filter,
) : super(
position: position,
maxEntries: maxEntries,
title: title,
orderCriterion: orderCriterion,
orderDirection: orderDirection,
filter: filter,
);
factory HomePlaylistsModel.fromMoor(MoorHomeWidget moorHomeWidget) {
final type = moorHomeWidget.type.toHomeWidgetType();
@ -14,7 +31,16 @@ class HomePlaylistsModel extends HomePlaylists implements HomeWidgetModel {
throw TypeError();
}
return HomePlaylistsModel(moorHomeWidget.position);
final data = json.decode(moorHomeWidget.data);
return HomePlaylistsModel(
moorHomeWidget.position,
data['maxEntries'] as int,
data['title'] as String,
(data['orderCriterion'] as String).toHomePlaylistsOrder()!,
(data['orderDirection'] as String).toOrderDirection()!,
(data['filter'] as String).toHomePlaylistsFilter()!,
);
}
factory HomePlaylistsModel.fromEntity(HomeWidget entity) {
@ -22,16 +48,29 @@ class HomePlaylistsModel extends HomePlaylists implements HomeWidgetModel {
throw TypeError();
}
entity as HomePlaylists;
return HomePlaylistsModel(entity.position);
return HomePlaylistsModel(
entity.position,
entity.maxEntries,
entity.title,
entity.orderCriterion,
entity.orderDirection,
entity.filter,
);
}
@override
HomeWidgetsCompanion toMoor() {
const data = '{}';
final Map<String, dynamic> data = {
'title': title,
'maxEntries': maxEntries,
'orderCriterion': orderCriterion.toString(),
'orderDirection': orderDirection.toString(),
'filter': filter.toString(),
};
return HomeWidgetsCompanion(
position: Value(position),
type: Value(type.toString()),
data: const Value(data),
data: Value(json.encode(data)),
);
}
}

View file

@ -9,7 +9,13 @@ import '../../datasources/moor_database.dart';
import 'home_widget_model.dart';
class HomeShuffleAllModel extends HomeShuffleAll implements HomeWidgetModel {
HomeShuffleAllModel(super.position, super.shuffleMode);
HomeShuffleAllModel(
int position,
ShuffleMode shuffleMode,
) : super(
position: position,
shuffleMode: shuffleMode,
);
factory HomeShuffleAllModel.fromMoor(MoorHomeWidget moorHomeWidget) {
final type = moorHomeWidget.type.toHomeWidgetType();
@ -32,11 +38,11 @@ class HomeShuffleAllModel extends HomeShuffleAll implements HomeWidgetModel {
@override
HomeWidgetsCompanion toMoor() {
final data = '{"shuffleMode": "$shuffleMode"}';
final data = {'shuffleMode': '$shuffleMode'};
return HomeWidgetsCompanion(
position: Value(position),
type: Value(type.toString()),
data: Value(data),
data: Value(json.encode(data)),
);
}
}

View file

@ -10,6 +10,9 @@ class PlaylistModel extends Playlist {
required String name,
required String iconString,
required String gradientString,
required DateTime timeCreated,
required DateTime timeChanged,
required DateTime timeLastPlayed,
ShuffleMode? shuffleMode,
}) : super(
id: id,
@ -17,6 +20,9 @@ class PlaylistModel extends Playlist {
iconString: iconString,
gradientString: gradientString,
shuffleMode: shuffleMode,
timeCreated: timeCreated,
timeChanged: timeChanged,
timeLastPlayed: timeLastPlayed,
);
factory PlaylistModel.fromPlaylist(Playlist playlist) {
@ -26,6 +32,9 @@ class PlaylistModel extends Playlist {
iconString: playlist.iconString,
gradientString: playlist.gradientString,
shuffleMode: playlist.shuffleMode,
timeChanged: playlist.timeChanged,
timeCreated: playlist.timeCreated,
timeLastPlayed: playlist.timeLastPlayed,
);
}
@ -36,6 +45,9 @@ class PlaylistModel extends Playlist {
iconString: moorPlaylist.icon,
gradientString: moorPlaylist.gradient,
shuffleMode: moorPlaylist.shuffleMode?.toShuffleMode(),
timeChanged: moorPlaylist.timeChanged,
timeCreated: moorPlaylist.timeCreated,
timeLastPlayed: moorPlaylist.timeLastPlayed,
);
}
@ -45,5 +57,8 @@ class PlaylistModel extends Playlist {
shuffleMode: m.Value(shuffleMode?.toString()),
icon: m.Value(iconString),
gradient: m.Value(gradientString),
timeChanged: m.Value(timeChanged),
timeCreated: m.Value(timeCreated),
timeLastPlayed: m.Value(timeLastPlayed),
);
}

View file

@ -1,5 +1,6 @@
import 'package:drift/drift.dart' as m;
import '../../domain/entities/enums.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../../domain/entities/smart_list.dart';
import '../datasources/moor_database.dart';
@ -11,6 +12,9 @@ class SmartListModel extends SmartList {
required String name,
required String iconString,
required String gradientString,
required DateTime timeCreated,
required DateTime timeChanged,
required DateTime timeLastPlayed,
required Filter filter,
required OrderBy orderBy,
ShuffleMode? shuffleMode,
@ -22,9 +26,12 @@ class SmartListModel extends SmartList {
shuffleMode: shuffleMode,
iconString: iconString,
gradientString: gradientString,
timeCreated: timeCreated,
timeChanged: timeChanged,
timeLastPlayed: timeLastPlayed,
);
factory SmartListModel.fromSmartList(SmartList smartList) {
factory SmartListModel.fromSmartList(SmartList smartList) {
return SmartListModel(
id: smartList.id,
name: smartList.name,
@ -33,6 +40,9 @@ class SmartListModel extends SmartList {
shuffleMode: smartList.shuffleMode,
iconString: smartList.iconString,
gradientString: smartList.gradientString,
timeCreated: smartList.timeCreated,
timeChanged: smartList.timeChanged,
timeLastPlayed: smartList.timeLastPlayed,
);
}
@ -49,7 +59,7 @@ class SmartListModel extends SmartList {
minYear: moorSmartList.minYear,
maxYear: moorSmartList.maxYear,
blockLevel: moorSmartList.blockLevel,
limit: moorSmartList.limit,
limit: moorSmartList.limit,
);
final orderBy = OrderBy(
@ -65,6 +75,9 @@ class SmartListModel extends SmartList {
shuffleMode: moorSmartList.shuffleMode?.toShuffleMode(),
iconString: moorSmartList.icon,
gradientString: moorSmartList.gradient,
timeChanged: moorSmartList.timeChanged,
timeCreated: moorSmartList.timeCreated,
timeLastPlayed: moorSmartList.timeLastPlayed,
);
}
@ -87,6 +100,9 @@ class SmartListModel extends SmartList {
orderDirections: m.Value(orderBy.orderDirections.join(',')),
icon: m.Value(iconString),
gradient: m.Value(gradientString),
timeChanged: m.Value(timeChanged),
timeCreated: m.Value(timeCreated),
timeLastPlayed: m.Value(timeLastPlayed),
);
List<SmartListArtistsCompanion> toMoorArtists() {

View file

@ -7,6 +7,9 @@ import 'package:string_similarity/string_similarity.dart';
import '../../constants.dart';
import '../../domain/entities/album.dart';
import '../../domain/entities/artist.dart';
import '../../domain/entities/custom_list.dart';
import '../../domain/entities/enums.dart';
import '../../domain/entities/home_widgets/playlists.dart';
import '../../domain/entities/playlist.dart';
import '../../domain/entities/shuffle_mode.dart';
import '../../domain/entities/smart_list.dart';
@ -492,4 +495,49 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
DateTime _day(DateTime dateTime) {
return DateTime(dateTime.year, dateTime.month, dateTime.day);
}
@override
Stream<List<CustomList>> getCustomListsStream({
HomePlaylistsOrder orderCriterion = HomePlaylistsOrder.name,
OrderDirection orderDirection = OrderDirection.ascending,
HomePlaylistsFilter filter = HomePlaylistsFilter.both,
int? limit,
}) {
final List<Stream<List<CustomList>>> streams = [];
if ([HomePlaylistsFilter.both, HomePlaylistsFilter.smartlists].contains(filter)) {
streams.add(_playlistDataSource.smartListsStream);
}
if ([HomePlaylistsFilter.both, HomePlaylistsFilter.playlists].contains(filter)) {
streams.add(_playlistDataSource.playlistsStream);
}
return Rx.combineLatest(streams, (List<List<CustomList>> lists) {
List<CustomList> combined = lists.expand((element) => element).toList();
switch (orderCriterion) {
case HomePlaylistsOrder.name:
combined.sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
break;
case HomePlaylistsOrder.creationDate:
combined.sort((a, b) => a.timeCreated.compareTo(b.timeCreated));
break;
case HomePlaylistsOrder.changeDate:
combined.sort((a, b) => a.timeChanged.compareTo(b.timeChanged));
break;
case HomePlaylistsOrder.history:
combined.sort((a, b) => a.timeLastPlayed.compareTo(b.timeLastPlayed));
break;
}
if (orderDirection == OrderDirection.descending) {
combined = combined.reversed.toList();
}
if (limit != null && limit > 0) {
combined = combined.take(limit).toList();
}
return combined;
});
}
}