initial support for home customization
This commit is contained in:
parent
71dc7bc803
commit
787690c6ea
7 changed files with 288 additions and 69 deletions
|
@ -6,11 +6,13 @@ import '../../domain/entities/home_widgets/artist_of_day.dart';
|
|||
import '../../domain/entities/home_widgets/home_widget.dart';
|
||||
import '../../domain/entities/home_widgets/shuffle_all.dart';
|
||||
import '../state/home_page_store.dart';
|
||||
import '../state/navigation_store.dart';
|
||||
import '../theming.dart';
|
||||
import '../widgets/highlight_album.dart';
|
||||
import '../widgets/highlight_artist.dart';
|
||||
import '../widgets/shuffle_all_button.dart';
|
||||
import '../widgets/smart_lists.dart';
|
||||
import 'home_settings_page.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({Key? key}) : super(key: key);
|
||||
|
@ -23,6 +25,7 @@ class _HomePageState extends State<HomePage> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final store = GetIt.I<HomePageStore>();
|
||||
final NavigationStore navStore = GetIt.I<NavigationStore>();
|
||||
|
||||
print('HomePage.build');
|
||||
return SafeArea(
|
||||
|
@ -31,6 +34,15 @@ class _HomePageState extends State<HomePage> {
|
|||
title: const Text(
|
||||
'Home',
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit_rounded),
|
||||
onPressed: () => navStore.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const HomeSettingsPage()),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Observer(
|
||||
builder: (context) {
|
||||
|
|
|
@ -1,22 +1,46 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:reorderables/reorderables.dart';
|
||||
|
||||
import '../../domain/entities/home_widgets/album_of_day.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 '../../domain/entities/shuffle_mode.dart';
|
||||
import '../state/home_page_store.dart';
|
||||
import '../state/navigation_store.dart';
|
||||
import '../theming.dart';
|
||||
import '../widgets/custom_modal_bottom_sheet.dart';
|
||||
|
||||
class HomeSettingsPage extends StatelessWidget {
|
||||
const HomeSettingsPage({Key? key}) : super(key: key);
|
||||
|
||||
static const titles = {
|
||||
HomeWidgetType.album_of_day: 'Album of the Day',
|
||||
HomeWidgetType.artist_of_day: 'Artist of the Day',
|
||||
HomeWidgetType.playlists: 'Playlists',
|
||||
HomeWidgetType.shuffle_all: 'Shuffle All',
|
||||
};
|
||||
|
||||
static const icons = {
|
||||
HomeWidgetType.album_of_day: Icons.album_rounded,
|
||||
HomeWidgetType.artist_of_day: Icons.person_rounded,
|
||||
HomeWidgetType.playlists: Icons.playlist_play_rounded,
|
||||
HomeWidgetType.shuffle_all: Icons.shuffle_rounded,
|
||||
};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final NavigationStore navStore = GetIt.I<NavigationStore>();
|
||||
final homeStore = GetIt.I<HomePageStore>();
|
||||
final navStore = GetIt.I<NavigationStore>();
|
||||
|
||||
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text(
|
||||
'Home Settings',
|
||||
'Home Customization',
|
||||
style: TEXT_HEADER,
|
||||
),
|
||||
leading: IconButton(
|
||||
|
@ -25,12 +49,146 @@ class HomeSettingsPage extends StatelessWidget {
|
|||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add_rounded),
|
||||
onPressed: () => navStore.pop(context),
|
||||
),
|
||||
icon: const Icon(Icons.add_rounded),
|
||||
onPressed: () => _onTapAdd(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
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(
|
||||
slivers: [
|
||||
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),
|
||||
),
|
||||
contentPadding: const EdgeInsets.fromLTRB(
|
||||
HORIZONTAL_PADDING,
|
||||
8.0,
|
||||
0.0,
|
||||
8.0,
|
||||
),
|
||||
);
|
||||
},
|
||||
childCount: widgetEntities.length,
|
||||
),
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
homeStore.moveHomeWidget(oldIndex, newIndex);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onTapAdd(BuildContext context) async {
|
||||
final homeStore = GetIt.I<HomePageStore>();
|
||||
final homeWidgets = homeStore.homeWidgetsStream.value ?? <HomeWidget>[];
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (context) => Observer(builder: (context) {
|
||||
return MyBottomSheet(
|
||||
widgets: [
|
||||
const ListTile(
|
||||
title: Text(
|
||||
'Add a Widget to Your Home Page',
|
||||
style: TEXT_HEADER_S,
|
||||
),
|
||||
tileColor: DARK2,
|
||||
),
|
||||
for (final type in HomeWidgetType.values)
|
||||
ListTile(
|
||||
title: Text(titles[type]!),
|
||||
leading: Icon(icons[type]),
|
||||
onTap: () {
|
||||
homeStore.addHomeWidget(
|
||||
_createHomeWidget(type, homeWidgets.length),
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
case HomeWidgetType.album_of_day:
|
||||
return HomeAlbumOfDay(position);
|
||||
case HomeWidgetType.artist_of_day:
|
||||
return HomeArtistOfDay(position, ShuffleMode.plus);
|
||||
case HomeWidgetType.playlists:
|
||||
return HomePlaylists(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,4 +21,13 @@ abstract class _HomePageStore with Store {
|
|||
@observable
|
||||
late ObservableStream<List<HomeWidget>> homeWidgetsStream =
|
||||
_homeWidgetRepository.homeWidgetsStream.asObservable();
|
||||
|
||||
Future<void> moveHomeWidget(int oldPosition, int newPosition) =>
|
||||
_homeWidgetRepository.moveHomeWidget(oldPosition, newPosition);
|
||||
|
||||
Future<void> addHomeWidget(HomeWidget homeWidget) =>
|
||||
_homeWidgetRepository.insertHomeWidget(homeWidget);
|
||||
|
||||
Future<void> removeHomeWidget(HomeWidget homeWidget) =>
|
||||
_homeWidgetRepository.removeHomeWidget(homeWidget);
|
||||
}
|
||||
|
|
|
@ -14,26 +14,34 @@ class MyBottomSheet extends StatelessWidget {
|
|||
return Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(
|
||||
color: DARK3,
|
||||
borderRadius: BorderRadius.circular(8.0),
|
||||
boxShadow: const [
|
||||
BoxShadow(color: Colors.black26, blurRadius: 8, offset: Offset(0, 1)),
|
||||
BoxShadow(
|
||||
color: Colors.black26,
|
||||
blurRadius: 8,
|
||||
offset: Offset(0, 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
// TODO: evaluate list view here
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: List.generate(count, (index) {
|
||||
if (index.isEven) {
|
||||
return widgets[(index / 2).round()];
|
||||
} else {
|
||||
return Container(
|
||||
height: 1,
|
||||
color: Colors.white10,
|
||||
);
|
||||
}
|
||||
}),
|
||||
child: Material(
|
||||
color: DARK25,
|
||||
child: ListView(
|
||||
// TODO: evaluate list view here
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
shrinkWrap: true,
|
||||
children: List.generate(count, (index) {
|
||||
if (index.isEven) {
|
||||
return widgets[index ~/ 2];
|
||||
} else {
|
||||
return Container(
|
||||
height: 1,
|
||||
color: DARK2,
|
||||
);
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -22,7 +22,7 @@ class ExcludeLevelOptions extends StatelessWidget {
|
|||
final lvl = _commonBlockLevel(songs);
|
||||
|
||||
return Container(
|
||||
color: Colors.white10,
|
||||
// color: Colors.white10,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: HORIZONTAL_PADDING - 12,
|
||||
|
|
|
@ -83,7 +83,7 @@ class _SongBottomSheetState extends State<SongBottomSheet> {
|
|||
final options = [
|
||||
const SizedBox.shrink(),
|
||||
Container(
|
||||
color: Colors.white10,
|
||||
// color: DARK3,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
|
@ -95,7 +95,7 @@ class _SongBottomSheetState extends State<SongBottomSheet> {
|
|||
contentPadding: const EdgeInsets.symmetric(horizontal: HORIZONTAL_PADDING),
|
||||
),
|
||||
),
|
||||
Container(width: 1.0, height: 24.0, color: Colors.white38),
|
||||
Container(width: 1.0, height: 24.0, color: DARK2),
|
||||
Expanded(
|
||||
child: SwitchListTile(
|
||||
title: const Text('Next'),
|
||||
|
@ -111,47 +111,54 @@ class _SongBottomSheetState extends State<SongBottomSheet> {
|
|||
];
|
||||
|
||||
final List<Widget> widgets = [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(HORIZONTAL_PADDING),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 64.0,
|
||||
height: 64.0,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(2.0),
|
||||
boxShadow: const [
|
||||
BoxShadow(color: Colors.black26, blurRadius: 8, offset: Offset(0, 1)),
|
||||
],
|
||||
image: DecorationImage(
|
||||
image: utils.getAlbumImage(song.albumArtPath),
|
||||
fit: BoxFit.fill,
|
||||
Container(
|
||||
color: DARK2,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(HORIZONTAL_PADDING),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 64.0,
|
||||
height: 64.0,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(2.0),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Colors.black26,
|
||||
blurRadius: 8,
|
||||
offset: Offset(0, 1),
|
||||
),
|
||||
],
|
||||
image: DecorationImage(
|
||||
image: utils.getAlbumImage(song.albumArtPath),
|
||||
fit: BoxFit.fill,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12.0),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
song.title,
|
||||
style: TEXT_HEADER_S,
|
||||
),
|
||||
const SizedBox(height: 4.0),
|
||||
Text(
|
||||
'#${song.trackNumber} • ${utils.msToTimeString(song.duration)} • ${song.year}',
|
||||
style: TEXT_SMALL_SUBTITLE,
|
||||
),
|
||||
Text(
|
||||
'played: ${song.playCount} • skipped: ${song.skipCount}',
|
||||
style: TEXT_SMALL_SUBTITLE,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
const SizedBox(width: 12.0),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
song.title,
|
||||
style: TEXT_HEADER_S,
|
||||
),
|
||||
const SizedBox(height: 4.0),
|
||||
Text(
|
||||
'#${song.trackNumber} • ${utils.msToTimeString(song.duration)} • ${song.year}',
|
||||
style: TEXT_SMALL_SUBTITLE,
|
||||
),
|
||||
Text(
|
||||
'played: ${song.playCount} • skipped: ${song.skipCount}',
|
||||
style: TEXT_SMALL_SUBTITLE,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
|
|
|
@ -30,15 +30,40 @@ class HomeWidgetDao extends DatabaseAccessor<MoorDatabase>
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> moveHomeWidget(int oldPosition, int newPosition) {
|
||||
// TODO: implement moveHomeWidget
|
||||
throw UnimplementedError();
|
||||
Future<void> moveHomeWidget(int oldPosition, int newPosition) async {
|
||||
if (oldPosition != newPosition) {
|
||||
transaction(() async {
|
||||
await (update(homeWidgets)..where((tbl) => tbl.position.equals(oldPosition)))
|
||||
.write(const HomeWidgetsCompanion(position: Value(-1)));
|
||||
if (oldPosition < newPosition) {
|
||||
for (int i = oldPosition + 1; i <= newPosition; i++) {
|
||||
await (update(homeWidgets)..where((tbl) => tbl.position.equals(i)))
|
||||
.write(HomeWidgetsCompanion(position: Value(i - 1)));
|
||||
}
|
||||
} else {
|
||||
for (int i = oldPosition - 1; i >= newPosition; i--) {
|
||||
await (update(homeWidgets)..where((tbl) => tbl.position.equals(i)))
|
||||
.write(HomeWidgetsCompanion(position: Value(i + 1)));
|
||||
}
|
||||
}
|
||||
await (update(homeWidgets)..where((tbl) => tbl.position.equals(-1)))
|
||||
.write(HomeWidgetsCompanion(position: Value(newPosition)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> removeHomeWidget(HomeWidgetModel homeWidget) {
|
||||
// TODO: implement removeHomeWidget
|
||||
throw UnimplementedError();
|
||||
Future<void> removeHomeWidget(HomeWidgetModel homeWidget) async {
|
||||
final entries = await select(homeWidgets).get();
|
||||
final count = entries.length;
|
||||
|
||||
transaction(() async {
|
||||
await (delete(homeWidgets)..where((tbl) => tbl.position.equals(homeWidget.position))).go();
|
||||
for (int i = homeWidget.position + 1; i < count; i++) {
|
||||
await (update(homeWidgets)..where((tbl) => tbl.position.equals(i)))
|
||||
.write(HomeWidgetsCompanion(position: Value(i - 1)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
Loading…
Add table
Reference in a new issue