mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-12-08 16:27:31 +00:00
Merge branch 'dev' into feat-jiosaavn
This commit is contained in:
commit
956a57b3f9
@ -163,6 +163,8 @@ class UserLocalTracks extends HookConsumerWidget {
|
||||
final searchFocus = useFocusNode();
|
||||
final isFiltering = useState(false);
|
||||
|
||||
final controller = useScrollController();
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
@ -256,7 +258,9 @@ class UserLocalTracks extends HookConsumerWidget {
|
||||
ref.refresh(localTracksProvider);
|
||||
},
|
||||
child: InterScrollbar(
|
||||
controller: controller,
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemCount: filteredTracks.length,
|
||||
itemBuilder: (context, index) {
|
||||
|
||||
@ -9,6 +9,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/playlist/playlist_create_dialog.dart';
|
||||
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
||||
import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
|
||||
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
||||
import 'package:spotube/components/playlist/playlist_card.dart';
|
||||
@ -81,68 +82,71 @@ class UserPlaylists extends HookConsumerWidget {
|
||||
return RefreshIndicator(
|
||||
onRefresh: playlistsQuery.refresh,
|
||||
child: SafeArea(
|
||||
child: CustomScrollView(
|
||||
child: InterScrollbar(
|
||||
controller: controller,
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: SearchBar(
|
||||
onChanged: (value) => searchText.value = value,
|
||||
hintText: context.l10n.filter_playlists,
|
||||
leading: const Icon(SpotubeIcons.filter),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
const PlaylistCreateDialogButton(),
|
||||
const SizedBox(width: 10),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(SpotubeIcons.magic),
|
||||
label: Text(context.l10n.generate_playlist),
|
||||
onPressed: () {
|
||||
GoRouter.of(context).push("/library/generate");
|
||||
},
|
||||
child: CustomScrollView(
|
||||
controller: controller,
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: SearchBar(
|
||||
onChanged: (value) => searchText.value = value,
|
||||
hintText: context.l10n.filter_playlists,
|
||||
leading: const Icon(SpotubeIcons.filter),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
const PlaylistCreateDialogButton(),
|
||||
const SizedBox(width: 10),
|
||||
ElevatedButton.icon(
|
||||
icon: const Icon(SpotubeIcons.magic),
|
||||
label: Text(context.l10n.generate_playlist),
|
||||
onPressed: () {
|
||||
GoRouter.of(context).push("/library/generate");
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(height: 10),
|
||||
),
|
||||
SliverGrid.builder(
|
||||
itemCount: playlists.length + 1,
|
||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 200,
|
||||
mainAxisExtent: DesktopTools.platform.isMobile ? 225 : 250,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 8,
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(height: 10),
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
if (index == playlists.length) {
|
||||
if (!playlistsQuery.hasNextPage) {
|
||||
return const SizedBox.shrink();
|
||||
SliverGrid.builder(
|
||||
itemCount: playlists.length + 1,
|
||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 200,
|
||||
mainAxisExtent: DesktopTools.platform.isMobile ? 225 : 250,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 8,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
if (index == playlists.length) {
|
||||
if (!playlistsQuery.hasNextPage) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Waypoint(
|
||||
controller: controller,
|
||||
isGrid: true,
|
||||
onTouchEdge: playlistsQuery.fetchNext,
|
||||
child: const ShimmerPlaybuttonCard(count: 1),
|
||||
);
|
||||
}
|
||||
|
||||
return Waypoint(
|
||||
controller: controller,
|
||||
isGrid: true,
|
||||
onTouchEdge: playlistsQuery.fetchNext,
|
||||
child: const ShimmerPlaybuttonCard(count: 1),
|
||||
);
|
||||
}
|
||||
|
||||
return PlaylistCard(playlists[index]);
|
||||
},
|
||||
)
|
||||
],
|
||||
return PlaylistCard(playlists[index]);
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -44,6 +44,7 @@ class PlayerQueue extends HookConsumerWidget {
|
||||
topRight: Radius.circular(10),
|
||||
);
|
||||
final theme = Theme.of(context);
|
||||
final mediaQuery = MediaQuery.of(context);
|
||||
final headlineColor = theme.textTheme.headlineSmall?.color;
|
||||
|
||||
final filteredTracks = useMemoized(
|
||||
@ -108,171 +109,166 @@ class PlayerQueue extends HookConsumerWidget {
|
||||
searchText.value = '';
|
||||
}
|
||||
},
|
||||
child: LayoutBuilder(builder: (context, constraints) {
|
||||
return Column(
|
||||
children: [
|
||||
if (!floating)
|
||||
Container(
|
||||
height: 5,
|
||||
width: 100,
|
||||
margin: const EdgeInsets.only(bottom: 5, top: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: headlineColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
if (!floating)
|
||||
Container(
|
||||
height: 5,
|
||||
width: 100,
|
||||
margin: const EdgeInsets.only(bottom: 5, top: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: headlineColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (constraints.mdAndUp || !isSearching.value) ...[
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
context.l10n.tracks_in_queue(tracks.length),
|
||||
style: TextStyle(
|
||||
color: headlineColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (mediaQuery.mdAndUp || !isSearching.value) ...[
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
context.l10n.tracks_in_queue(tracks.length),
|
||||
style: TextStyle(
|
||||
color: headlineColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
if (mediaQuery.mdAndUp || isSearching.value)
|
||||
TextField(
|
||||
onChanged: (value) {
|
||||
searchText.value = value;
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
hintText: context.l10n.search,
|
||||
isDense: true,
|
||||
prefixIcon: mediaQuery.smAndDown
|
||||
? IconButton(
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios_new_outlined,
|
||||
),
|
||||
onPressed: () {
|
||||
isSearching.value = false;
|
||||
searchText.value = '';
|
||||
},
|
||||
style: IconButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
minimumSize: const Size.square(20),
|
||||
),
|
||||
)
|
||||
: const Icon(SpotubeIcons.filter),
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: 40,
|
||||
maxWidth: mediaQuery.smAndDown
|
||||
? mediaQuery.size.width - 40
|
||||
: 300,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
if (constraints.mdAndUp || isSearching.value)
|
||||
TextField(
|
||||
onChanged: (value) {
|
||||
searchText.value = value;
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
hintText: context.l10n.search,
|
||||
isDense: true,
|
||||
prefixIcon: constraints.smAndDown
|
||||
? IconButton(
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios_new_outlined,
|
||||
),
|
||||
onPressed: () {
|
||||
isSearching.value = false;
|
||||
searchText.value = '';
|
||||
},
|
||||
style: IconButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
minimumSize: const Size.square(20),
|
||||
),
|
||||
)
|
||||
: const Icon(SpotubeIcons.filter),
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: 40,
|
||||
maxWidth: constraints.smAndDown
|
||||
? constraints.maxWidth - 20
|
||||
: 300,
|
||||
)
|
||||
else
|
||||
IconButton.filledTonal(
|
||||
icon: const Icon(SpotubeIcons.filter),
|
||||
onPressed: () {
|
||||
isSearching.value = !isSearching.value;
|
||||
},
|
||||
),
|
||||
if (mediaQuery.mdAndUp || !isSearching.value) ...[
|
||||
const SizedBox(width: 10),
|
||||
FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor:
|
||||
theme.scaffoldBackgroundColor.withOpacity(0.5),
|
||||
foregroundColor: theme.textTheme.headlineSmall?.color,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(SpotubeIcons.playlistRemove),
|
||||
const SizedBox(width: 5),
|
||||
Text(context.l10n.clear_all),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
playlistNotifier.stop();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (!isSearching.value && searchText.value.isEmpty)
|
||||
Flexible(
|
||||
child: ReorderableListView.builder(
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
playlistNotifier.moveTrack(oldIndex, newIndex);
|
||||
},
|
||||
scrollController: controller,
|
||||
itemCount: tracks.length,
|
||||
shrinkWrap: true,
|
||||
buildDefaultDragHandles: false,
|
||||
itemBuilder: (context, i) {
|
||||
final track = tracks.elementAt(i);
|
||||
return AutoScrollTag(
|
||||
key: ValueKey(i),
|
||||
controller: controller,
|
||||
index: i,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: TrackTile(
|
||||
index: i,
|
||||
track: track,
|
||||
onTap: () async {
|
||||
if (playlist.activeTrack?.id == track.id) {
|
||||
return;
|
||||
}
|
||||
await playlistNotifier.jumpToTrack(track);
|
||||
},
|
||||
leadingActions: [
|
||||
ReorderableDragStartListener(
|
||||
index: i,
|
||||
child: const Icon(SpotubeIcons.dragHandle),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
IconButton.filledTonal(
|
||||
icon: const Icon(SpotubeIcons.filter),
|
||||
onPressed: () {
|
||||
isSearching.value = !isSearching.value;
|
||||
},
|
||||
),
|
||||
if (constraints.mdAndUp || !isSearching.value) ...[
|
||||
const SizedBox(width: 10),
|
||||
FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor:
|
||||
theme.scaffoldBackgroundColor.withOpacity(0.5),
|
||||
foregroundColor:
|
||||
theme.textTheme.headlineSmall?.color,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(SpotubeIcons.playlistRemove),
|
||||
const SizedBox(width: 5),
|
||||
Text(context.l10n.clear_all),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
playlistNotifier.stop();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
if (!isSearching.value && searchText.value.isEmpty)
|
||||
Flexible(
|
||||
child: InterScrollbar(
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
else
|
||||
Flexible(
|
||||
child: InterScrollbar(
|
||||
controller: controller,
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
child: ReorderableListView.builder(
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
playlistNotifier.moveTrack(oldIndex, newIndex);
|
||||
},
|
||||
scrollController: controller,
|
||||
itemCount: tracks.length,
|
||||
shrinkWrap: true,
|
||||
buildDefaultDragHandles: false,
|
||||
itemBuilder: (context, i) {
|
||||
final track = tracks.elementAt(i);
|
||||
return AutoScrollTag(
|
||||
key: ValueKey(i),
|
||||
controller: controller,
|
||||
itemCount: filteredTracks.length,
|
||||
itemBuilder: (context, i) {
|
||||
final track = filteredTracks.elementAt(i);
|
||||
return Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: TrackTile(
|
||||
index: i,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: TrackTile(
|
||||
index: i,
|
||||
track: track,
|
||||
onTap: () async {
|
||||
if (playlist.activeTrack?.id == track.id) {
|
||||
return;
|
||||
}
|
||||
await playlistNotifier.jumpToTrack(track);
|
||||
},
|
||||
leadingActions: [
|
||||
ReorderableDragStartListener(
|
||||
index: i,
|
||||
child:
|
||||
const Icon(SpotubeIcons.dragHandle),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
Flexible(
|
||||
child: InterScrollbar(
|
||||
child: ListView.builder(
|
||||
itemCount: filteredTracks.length,
|
||||
itemBuilder: (context, i) {
|
||||
final track = filteredTracks.elementAt(i);
|
||||
return Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: TrackTile(
|
||||
index: i,
|
||||
track: track,
|
||||
onTap: () async {
|
||||
if (playlist.activeTrack?.id == track.id) {
|
||||
return;
|
||||
}
|
||||
await playlistNotifier.jumpToTrack(track);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
track: track,
|
||||
onTap: () async {
|
||||
if (playlist.activeTrack?.id == track.id) {
|
||||
return;
|
||||
}
|
||||
await playlistNotifier.jumpToTrack(track);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -56,7 +56,9 @@ class SiblingTracksSheet extends HookConsumerWidget {
|
||||
useValueListenable(searchController).text,
|
||||
);
|
||||
|
||||
final searchRequest = useMemoized<Future<List<SourceInfo>>>(() async {
|
||||
final controller = useScrollController();
|
||||
|
||||
final searchRequest = useMemoized(() async {
|
||||
if (searchTerm.trim().isEmpty) {
|
||||
return <SourceInfo>[];
|
||||
}
|
||||
@ -219,8 +221,10 @@ class SiblingTracksSheet extends HookConsumerWidget {
|
||||
transitionBuilder: (child, animation) =>
|
||||
FadeTransition(opacity: animation, child: child),
|
||||
child: InterScrollbar(
|
||||
controller: controller,
|
||||
child: switch (isSearching.value) {
|
||||
false => ListView.builder(
|
||||
controller: controller,
|
||||
itemCount: siblings.length,
|
||||
itemBuilder: (context, index) =>
|
||||
itemBuilder(siblings[index]),
|
||||
@ -238,7 +242,9 @@ class SiblingTracksSheet extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
return InterScrollbar(
|
||||
controller: controller,
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
itemCount: snapshot.data!.length,
|
||||
itemBuilder: (context, index) =>
|
||||
itemBuilder(snapshot.data![index]),
|
||||
|
||||
@ -49,6 +49,7 @@ class ColorSchemePickerDialog extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final preferences = ref.watch(userPreferencesProvider);
|
||||
final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
|
||||
final scheme = preferences.accentColorScheme;
|
||||
final active = useState<String>(colorsMap.firstWhere(
|
||||
(element) {
|
||||
@ -57,7 +58,7 @@ class ColorSchemePickerDialog extends HookConsumerWidget {
|
||||
).name);
|
||||
|
||||
onOk() {
|
||||
preferences.setAccentColorScheme(
|
||||
preferencesNotifier.setAccentColorScheme(
|
||||
colorsMap.firstWhere(
|
||||
(element) {
|
||||
return element.name == active.value;
|
||||
|
||||
@ -1,29 +1,16 @@
|
||||
import 'package:draggable_scrollbar/draggable_scrollbar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
|
||||
class InterScrollbar extends HookWidget {
|
||||
final Widget child;
|
||||
final ScrollController? controller;
|
||||
final bool? thumbVisibility;
|
||||
final bool? trackVisibility;
|
||||
final double? thickness;
|
||||
final Radius? radius;
|
||||
final bool Function(ScrollNotification)? notificationPredicate;
|
||||
final bool? interactive;
|
||||
final ScrollbarOrientation? scrollbarOrientation;
|
||||
final ScrollController controller;
|
||||
|
||||
const InterScrollbar({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.controller,
|
||||
this.thumbVisibility,
|
||||
this.trackVisibility,
|
||||
this.thickness,
|
||||
this.radius,
|
||||
this.notificationPredicate,
|
||||
this.interactive,
|
||||
this.scrollbarOrientation,
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -32,38 +19,9 @@ class InterScrollbar extends HookWidget {
|
||||
|
||||
if (DesktopTools.platform.isDesktop) return child;
|
||||
|
||||
return ScrollbarTheme(
|
||||
data: theme.scrollbarTheme.copyWith(
|
||||
crossAxisMargin: 10,
|
||||
minThumbLength: 80,
|
||||
thickness: MaterialStateProperty.resolveWith((states) {
|
||||
if (states.contains(MaterialState.hovered) ||
|
||||
states.contains(MaterialState.dragged) ||
|
||||
states.contains(MaterialState.pressed)) {
|
||||
return 40;
|
||||
}
|
||||
return 20;
|
||||
}),
|
||||
radius: const Radius.circular(20),
|
||||
thumbColor: MaterialStateProperty.resolveWith((states) {
|
||||
if (states.contains(MaterialState.hovered) ||
|
||||
states.contains(MaterialState.dragged)) {
|
||||
return theme.colorScheme.onSurface.withOpacity(0.5);
|
||||
}
|
||||
return theme.colorScheme.onSurface.withOpacity(0.3);
|
||||
}),
|
||||
),
|
||||
child: Scrollbar(
|
||||
controller: controller,
|
||||
thumbVisibility: thumbVisibility,
|
||||
trackVisibility: trackVisibility,
|
||||
thickness: thickness,
|
||||
radius: radius,
|
||||
notificationPredicate: notificationPredicate,
|
||||
interactive: interactive ?? true,
|
||||
scrollbarOrientation: scrollbarOrientation,
|
||||
child: child,
|
||||
),
|
||||
return DraggableScrollbar.semicircle(
|
||||
controller: controller,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,28 +79,25 @@ class GenrePage extends HookConsumerWidget {
|
||||
const ShimmerCategories()
|
||||
else
|
||||
Expanded(
|
||||
child: InterScrollbar(
|
||||
child: ListView.builder(
|
||||
controller: scrollController,
|
||||
child: ListView.builder(
|
||||
controller: scrollController,
|
||||
itemCount: categories.length,
|
||||
itemBuilder: (context, index) {
|
||||
return AnimatedSwitcher(
|
||||
transitionBuilder: (child, animation) {
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: searchController.text.isEmpty &&
|
||||
index == categories.length - 1 &&
|
||||
categoriesQuery.hasNextPage
|
||||
? const ShimmerCategories()
|
||||
: CategoryCard(categories[index]),
|
||||
);
|
||||
},
|
||||
),
|
||||
itemCount: categories.length,
|
||||
itemBuilder: (context, index) {
|
||||
return AnimatedSwitcher(
|
||||
transitionBuilder: (child, animation) {
|
||||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: searchController.text.isEmpty &&
|
||||
index == categories.length - 1 &&
|
||||
categoriesQuery.hasNextPage
|
||||
? const ShimmerCategories()
|
||||
: CategoryCard(categories[index]),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@ -4,7 +4,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/components/shared/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart';
|
||||
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
||||
import 'package:spotube/components/shared/shimmers/shimmer_categories.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/provider/authentication_provider.dart';
|
||||
@ -47,47 +46,45 @@ class PersonalizedPage extends HookConsumerWidget {
|
||||
[newReleases.pages],
|
||||
);
|
||||
|
||||
return InterScrollbar(
|
||||
return ListView(
|
||||
controller: controller,
|
||||
child: ListView(
|
||||
controller: controller,
|
||||
children: [
|
||||
if (!featuredPlaylistsQuery.hasPageData &&
|
||||
!featuredPlaylistsQuery.isLoadingNextPage)
|
||||
const ShimmerCategories()
|
||||
else
|
||||
HorizontalPlaybuttonCardView<PlaylistSimple>(
|
||||
items: playlists.toList(),
|
||||
title: Text(context.l10n.featured),
|
||||
hasNextPage: featuredPlaylistsQuery.hasNextPage,
|
||||
onFetchMore: featuredPlaylistsQuery.fetchNext,
|
||||
),
|
||||
if (auth != null &&
|
||||
newReleases.hasPageData &&
|
||||
userArtistsQuery.hasData)
|
||||
HorizontalPlaybuttonCardView<Album>(
|
||||
items: albums,
|
||||
title: Text(context.l10n.new_releases),
|
||||
hasNextPage: newReleases.hasNextPage,
|
||||
onFetchMore: newReleases.fetchNext,
|
||||
),
|
||||
...?madeForUser.data?["content"]?["items"]?.map((item) {
|
||||
final playlists = item["content"]?["items"]
|
||||
?.where((itemL2) => itemL2["type"] == "playlist")
|
||||
.map((itemL2) => PlaylistSimple.fromJson(itemL2))
|
||||
.toList()
|
||||
.cast<PlaylistSimple>() ??
|
||||
<PlaylistSimple>[];
|
||||
if (playlists.isEmpty) return const SizedBox.shrink();
|
||||
return HorizontalPlaybuttonCardView<PlaylistSimple>(
|
||||
items: playlists,
|
||||
title: Text(item["name"] ?? ""),
|
||||
hasNextPage: false,
|
||||
onFetchMore: () {},
|
||||
);
|
||||
})
|
||||
],
|
||||
),
|
||||
children: [
|
||||
if (!featuredPlaylistsQuery.hasPageData &&
|
||||
!featuredPlaylistsQuery.isLoadingNextPage)
|
||||
const ShimmerCategories()
|
||||
else
|
||||
HorizontalPlaybuttonCardView<PlaylistSimple>(
|
||||
items: playlists.toList(),
|
||||
title: Text(context.l10n.featured),
|
||||
hasNextPage: featuredPlaylistsQuery.hasNextPage,
|
||||
onFetchMore: featuredPlaylistsQuery.fetchNext,
|
||||
),
|
||||
if (auth != null &&
|
||||
newReleases.hasPageData &&
|
||||
userArtistsQuery.hasData &&
|
||||
!newReleases.isLoadingNextPage)
|
||||
HorizontalPlaybuttonCardView<Album>(
|
||||
items: albums,
|
||||
title: Text(context.l10n.new_releases),
|
||||
hasNextPage: newReleases.hasNextPage,
|
||||
onFetchMore: newReleases.fetchNext,
|
||||
),
|
||||
...?madeForUser.data?["content"]?["items"]?.map((item) {
|
||||
final playlists = item["content"]?["items"]
|
||||
?.where((itemL2) => itemL2["type"] == "playlist")
|
||||
.map((itemL2) => PlaylistSimple.fromJson(itemL2))
|
||||
.toList()
|
||||
.cast<PlaylistSimple>() ??
|
||||
<PlaylistSimple>[];
|
||||
if (playlists.isEmpty) return const SizedBox.shrink();
|
||||
return HorizontalPlaybuttonCardView<PlaylistSimple>(
|
||||
items: playlists,
|
||||
title: Text(item["name"] ?? ""),
|
||||
hasNextPage: false,
|
||||
onFetchMore: () {},
|
||||
);
|
||||
})
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,26 +71,32 @@ class SearchPage extends HookConsumerWidget {
|
||||
searchTerm.isNotEmpty;
|
||||
|
||||
final resultWidget = HookBuilder(
|
||||
builder: (context) => InterScrollbar(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SearchTracksSection(query: searchTrack),
|
||||
SearchPlaylistsSection(query: searchPlaylist),
|
||||
const SizedBox(height: 20),
|
||||
SearchArtistsSection(query: searchArtist),
|
||||
const SizedBox(height: 20),
|
||||
SearchAlbumsSection(query: searchAlbum),
|
||||
],
|
||||
builder: (context) {
|
||||
final controller = useScrollController();
|
||||
|
||||
return InterScrollbar(
|
||||
controller: controller,
|
||||
child: SingleChildScrollView(
|
||||
controller: controller,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SearchTracksSection(query: searchTrack),
|
||||
SearchPlaylistsSection(query: searchPlaylist),
|
||||
const SizedBox(height: 20),
|
||||
SearchArtistsSection(query: searchArtist),
|
||||
const SizedBox(height: 20),
|
||||
SearchAlbumsSection(query: searchAlbum),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return SafeArea(
|
||||
|
||||
@ -15,6 +15,7 @@ class BlackListPage extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final controller = useScrollController();
|
||||
final blacklist = ref.watch(BlackListNotifier.provider);
|
||||
final searchText = useState("");
|
||||
|
||||
@ -58,7 +59,9 @@ class BlackListPage extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
InterScrollbar(
|
||||
controller: controller,
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
shrinkWrap: true,
|
||||
itemCount: filteredBlacklist.length,
|
||||
itemBuilder: (context, index) {
|
||||
|
||||
@ -52,6 +52,7 @@ class LogsPage extends HookWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = useScrollController();
|
||||
final logs = useState<List<({DateTime? date, String body})>>([]);
|
||||
final rawLogs = useRef<String>("");
|
||||
final path = useRef<File?>(null);
|
||||
@ -93,7 +94,9 @@ class LogsPage extends HookWidget {
|
||||
),
|
||||
body: SafeArea(
|
||||
child: InterScrollbar(
|
||||
controller: controller,
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
itemCount: logs.value.length,
|
||||
itemBuilder: (context, index) {
|
||||
final log = logs.value[index];
|
||||
|
||||
@ -16,6 +16,7 @@ class SettingsAboutSection extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final preferences = ref.watch(userPreferencesProvider);
|
||||
final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
|
||||
|
||||
return SectionCardWithHeading(
|
||||
heading: context.l10n.about,
|
||||
@ -68,7 +69,7 @@ class SettingsAboutSection extends HookConsumerWidget {
|
||||
secondary: const Icon(SpotubeIcons.update),
|
||||
title: Text(context.l10n.check_for_updates),
|
||||
value: preferences.checkUpdate,
|
||||
onChanged: (checked) => preferences.setCheckUpdate(checked),
|
||||
onChanged: (checked) => preferencesNotifier.setCheckUpdate(checked),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(SpotubeIcons.info),
|
||||
|
||||
@ -15,6 +15,7 @@ class SettingsAppearanceSection extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final preferences = ref.watch(userPreferencesProvider);
|
||||
final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
|
||||
final pickColorScheme = useCallback(() {
|
||||
return () => showDialog(
|
||||
context: context,
|
||||
@ -33,7 +34,7 @@ class SettingsAppearanceSection extends HookConsumerWidget {
|
||||
value: preferences.layoutMode,
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
preferences.setLayoutMode(value);
|
||||
preferencesNotifier.setLayoutMode(value);
|
||||
}
|
||||
},
|
||||
options: [
|
||||
@ -71,7 +72,7 @@ class SettingsAppearanceSection extends HookConsumerWidget {
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
preferences.setThemeMode(value);
|
||||
preferencesNotifier.setThemeMode(value);
|
||||
}
|
||||
},
|
||||
),
|
||||
@ -80,7 +81,7 @@ class SettingsAppearanceSection extends HookConsumerWidget {
|
||||
title: Text(context.l10n.use_amoled_mode),
|
||||
subtitle: Text(context.l10n.pitch_dark_theme),
|
||||
value: preferences.amoledDarkTheme,
|
||||
onChanged: preferences.setAmoledDarkTheme,
|
||||
onChanged: preferencesNotifier.setAmoledDarkTheme,
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(SpotubeIcons.palette),
|
||||
@ -101,7 +102,7 @@ class SettingsAppearanceSection extends HookConsumerWidget {
|
||||
title: Text(context.l10n.sync_album_color),
|
||||
subtitle: Text(context.l10n.sync_album_color_description),
|
||||
value: preferences.albumColorSync,
|
||||
onChanged: preferences.setAlbumColorSync,
|
||||
onChanged: preferencesNotifier.setAlbumColorSync,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@ -12,6 +12,7 @@ class SettingsDesktopSection extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final preferences = ref.watch(userPreferencesProvider);
|
||||
final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
|
||||
|
||||
return SectionCardWithHeading(
|
||||
heading: context.l10n.desktop,
|
||||
@ -32,7 +33,7 @@ class SettingsDesktopSection extends HookConsumerWidget {
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
preferences.setCloseBehavior(value);
|
||||
preferencesNotifier.setCloseBehavior(value);
|
||||
}
|
||||
},
|
||||
),
|
||||
@ -40,13 +41,13 @@ class SettingsDesktopSection extends HookConsumerWidget {
|
||||
secondary: const Icon(SpotubeIcons.tray),
|
||||
title: Text(context.l10n.show_tray_icon),
|
||||
value: preferences.showSystemTrayIcon,
|
||||
onChanged: preferences.setShowSystemTrayIcon,
|
||||
onChanged: preferencesNotifier.setShowSystemTrayIcon,
|
||||
),
|
||||
SwitchListTile(
|
||||
secondary: const Icon(SpotubeIcons.window),
|
||||
title: Text(context.l10n.use_system_title_bar),
|
||||
value: preferences.systemTitleBar,
|
||||
onChanged: preferences.setSystemTitleBar,
|
||||
onChanged: preferencesNotifier.setSystemTitleBar,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@ -14,6 +14,7 @@ class SettingsDownloadsSection extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
|
||||
final preferences = ref.watch(userPreferencesProvider);
|
||||
|
||||
final pickDownloadLocation = useCallback(() async {
|
||||
@ -22,13 +23,13 @@ class SettingsDownloadsSection extends HookConsumerWidget {
|
||||
initialDirectory: preferences.downloadLocation,
|
||||
);
|
||||
if (dirStr == null) return;
|
||||
preferences.setDownloadLocation(dirStr);
|
||||
preferencesNotifier.setDownloadLocation(dirStr);
|
||||
} else {
|
||||
String? dirStr = await getDirectoryPath(
|
||||
initialDirectory: preferences.downloadLocation,
|
||||
);
|
||||
if (dirStr == null) return;
|
||||
preferences.setDownloadLocation(dirStr);
|
||||
preferencesNotifier.setDownloadLocation(dirStr);
|
||||
}
|
||||
}, [preferences.downloadLocation]);
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@ class SettingsLanguageRegionSection extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final preferences = ref.watch(userPreferencesProvider);
|
||||
final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
|
||||
final mediaQuery = MediaQuery.of(context);
|
||||
|
||||
return SectionCardWithHeading(
|
||||
@ -26,7 +27,7 @@ class SettingsLanguageRegionSection extends HookConsumerWidget {
|
||||
value: preferences.locale,
|
||||
onChanged: (locale) {
|
||||
if (locale == null) return;
|
||||
preferences.setLocale(locale);
|
||||
preferencesNotifier.setLocale(locale);
|
||||
},
|
||||
title: Text(context.l10n.language),
|
||||
secondary: const Icon(SpotubeIcons.language),
|
||||
@ -57,7 +58,7 @@ class SettingsLanguageRegionSection extends HookConsumerWidget {
|
||||
value: preferences.recommendationMarket,
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
preferences.setRecommendationMarket(value);
|
||||
preferencesNotifier.setRecommendationMarket(value);
|
||||
},
|
||||
options: spotifyMarkets
|
||||
.map(
|
||||
|
||||
@ -18,6 +18,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final preferences = ref.watch(userPreferencesProvider);
|
||||
final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return SectionCardWithHeading(
|
||||
@ -43,7 +44,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
preferences.setAudioQuality(value);
|
||||
preferencesNotifier.setAudioQuality(value);
|
||||
}
|
||||
},
|
||||
),
|
||||
@ -59,7 +60,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
preferences.setAudioSource(value);
|
||||
preferencesNotifier.setAudioSource(value);
|
||||
},
|
||||
),
|
||||
AnimatedSwitcher(
|
||||
@ -117,7 +118,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
preferences.setPipedInstance(value);
|
||||
preferencesNotifier.setPipedInstance(value);
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -145,7 +146,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
preferences.setSearchMode(value);
|
||||
preferencesNotifier.setSearchMode(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
@ -159,7 +160,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
title: Text(context.l10n.skip_non_music),
|
||||
value: preferences.skipNonMusic,
|
||||
onChanged: (state) {
|
||||
preferences.setSkipNonMusic(state);
|
||||
preferencesNotifier.setSkipNonMusic(state);
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
@ -177,7 +178,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
secondary: const Icon(SpotubeIcons.normalize),
|
||||
title: Text(context.l10n.normalize_audio),
|
||||
value: preferences.normalizeAudio,
|
||||
onChanged: preferences.setNormalizeAudio,
|
||||
onChanged: preferencesNotifier.setNormalizeAudio,
|
||||
),
|
||||
if (preferences.audioSource != AudioSource.jiosaavn)
|
||||
AdaptiveSelectTile<SourceCodecs>(
|
||||
@ -196,7 +197,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
preferences.setStreamMusicCodec(value);
|
||||
preferencesNotifier.setStreamMusicCodec(value);
|
||||
},
|
||||
),
|
||||
if (preferences.audioSource != AudioSource.jiosaavn)
|
||||
@ -216,7 +217,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value == null) return;
|
||||
preferences.setDownloadMusicCodec(value);
|
||||
preferencesNotifier.setDownloadMusicCodec(value);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
||||
@ -20,7 +21,8 @@ class SettingsPage extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final preferences = ref.watch(userPreferencesProvider);
|
||||
final controller = useScrollController();
|
||||
final preferencesNotifier = ref.watch(userPreferencesProvider.notifier);
|
||||
|
||||
return SafeArea(
|
||||
bottom: false,
|
||||
@ -36,7 +38,9 @@ class SettingsPage extends HookConsumerWidget {
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 1366),
|
||||
child: InterScrollbar(
|
||||
controller: controller,
|
||||
child: ListView(
|
||||
controller: controller,
|
||||
children: [
|
||||
const SettingsAccountSection(),
|
||||
const SettingsLanguageRegionSection(),
|
||||
@ -49,7 +53,7 @@ class SettingsPage extends HookConsumerWidget {
|
||||
const SettingsAboutSection(),
|
||||
Center(
|
||||
child: FilledButton(
|
||||
onPressed: preferences.reset,
|
||||
onPressed: preferencesNotifier.reset,
|
||||
child: Text(context.l10n.restore_defaults),
|
||||
),
|
||||
),
|
||||
|
||||
@ -217,6 +217,26 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
|
||||
Catcher2.reportCheckedError(e, stackTrace);
|
||||
}
|
||||
});
|
||||
|
||||
String? lastScrobbled;
|
||||
audioPlayer.positionStream.listen((position) {
|
||||
try {
|
||||
final uid = state.activeTrack is LocalTrack
|
||||
? (state.activeTrack as LocalTrack).path
|
||||
: state.activeTrack?.id;
|
||||
|
||||
if (state.activeTrack == null ||
|
||||
lastScrobbled == uid ||
|
||||
position.inSeconds < 30) {
|
||||
return;
|
||||
}
|
||||
|
||||
scrobbler.scrobble(state.activeTrack!);
|
||||
lastScrobbled = uid;
|
||||
} catch (e, stack) {
|
||||
Catcher2.reportCheckedError(e, stack);
|
||||
}
|
||||
});
|
||||
}();
|
||||
}
|
||||
|
||||
@ -631,30 +651,12 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
|
||||
|
||||
@override
|
||||
set state(state) {
|
||||
final hasActiveTrackChanged = super.state.activeTrack is SourcedTrack
|
||||
? state.activeTrack?.id != super.state.activeTrack?.id
|
||||
: super.state.activeTrack is LocalTrack &&
|
||||
state.activeTrack is LocalTrack
|
||||
? (super.state.activeTrack as LocalTrack).path !=
|
||||
(state.activeTrack as LocalTrack).path
|
||||
: super.state.activeTrack?.id != state.activeTrack?.id;
|
||||
|
||||
final oldTrack = super.state.activeTrack;
|
||||
|
||||
super.state = state;
|
||||
if (state.tracks.isEmpty && ref.read(paletteProvider) != null) {
|
||||
ref.read(paletteProvider.notifier).state = null;
|
||||
} else {
|
||||
updatePalette();
|
||||
}
|
||||
audioPlayer.position.then((position) {
|
||||
final isMoreThan30secs = position != null &&
|
||||
(position == Duration.zero || position.inSeconds > 30);
|
||||
|
||||
if (hasActiveTrackChanged && oldTrack != null && isMoreThan30secs) {
|
||||
scrobbler.scrobble(oldTrack);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
@ -14,7 +13,7 @@ import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||
import 'package:spotube/services/sourced_track/enums.dart';
|
||||
|
||||
import 'package:spotube/utils/persisted_change_notifier.dart';
|
||||
import 'package:spotube/utils/persisted_state_notifier.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
@ -64,220 +63,103 @@ enum SearchMode {
|
||||
}
|
||||
}
|
||||
|
||||
class UserPreferences extends PersistedChangeNotifier {
|
||||
SourceQualities audioQuality;
|
||||
bool albumColorSync;
|
||||
bool amoledDarkTheme;
|
||||
bool checkUpdate;
|
||||
bool normalizeAudio;
|
||||
bool showSystemTrayIcon;
|
||||
bool skipNonMusic;
|
||||
bool systemTitleBar;
|
||||
CloseBehavior closeBehavior;
|
||||
late SpotubeColor accentColorScheme;
|
||||
LayoutMode layoutMode;
|
||||
Locale locale;
|
||||
Market recommendationMarket;
|
||||
SearchMode searchMode;
|
||||
class UserPreferences {
|
||||
final SourceQualities audioQuality;
|
||||
final bool albumColorSync;
|
||||
final bool amoledDarkTheme;
|
||||
final bool checkUpdate;
|
||||
final bool normalizeAudio;
|
||||
final bool showSystemTrayIcon;
|
||||
final bool skipNonMusic;
|
||||
final bool systemTitleBar;
|
||||
final CloseBehavior closeBehavior;
|
||||
final SpotubeColor accentColorScheme;
|
||||
final LayoutMode layoutMode;
|
||||
final Locale locale;
|
||||
final Market recommendationMarket;
|
||||
final SearchMode searchMode;
|
||||
String downloadLocation;
|
||||
String pipedInstance;
|
||||
ThemeMode themeMode;
|
||||
AudioSource audioSource;
|
||||
SourceCodecs streamMusicCodec;
|
||||
SourceCodecs downloadMusicCodec;
|
||||
final String pipedInstance;
|
||||
final ThemeMode themeMode;
|
||||
final AudioSource audioSource;
|
||||
final SourceCodecs streamMusicCodec;
|
||||
final SourceCodecs downloadMusicCodec;
|
||||
|
||||
final Ref ref;
|
||||
|
||||
UserPreferences(
|
||||
this.ref, {
|
||||
this.recommendationMarket = Market.US,
|
||||
this.themeMode = ThemeMode.system,
|
||||
this.layoutMode = LayoutMode.adaptive,
|
||||
this.albumColorSync = true,
|
||||
this.checkUpdate = true,
|
||||
this.audioQuality = SourceQualities.high,
|
||||
this.downloadLocation = "",
|
||||
this.closeBehavior = CloseBehavior.close,
|
||||
this.showSystemTrayIcon = true,
|
||||
this.locale = const Locale("system", "system"),
|
||||
this.pipedInstance = "https://pipedapi.kavin.rocks",
|
||||
this.searchMode = SearchMode.youtube,
|
||||
this.skipNonMusic = true,
|
||||
this.audioSource = AudioSource.youtube,
|
||||
this.systemTitleBar = false,
|
||||
this.amoledDarkTheme = false,
|
||||
this.normalizeAudio = true,
|
||||
this.streamMusicCodec = SourceCodecs.weba,
|
||||
this.downloadMusicCodec = SourceCodecs.m4a,
|
||||
SpotubeColor? accentColorScheme,
|
||||
}) : super() {
|
||||
this.accentColorScheme =
|
||||
accentColorScheme ?? SpotubeColor(Colors.blue.value, name: "Blue");
|
||||
if (downloadLocation.isEmpty && !kIsWeb) {
|
||||
UserPreferences({
|
||||
required SourceQualities? audioQuality,
|
||||
required bool? albumColorSync,
|
||||
required bool? amoledDarkTheme,
|
||||
required bool? checkUpdate,
|
||||
required bool? normalizeAudio,
|
||||
required bool? showSystemTrayIcon,
|
||||
required bool? skipNonMusic,
|
||||
required bool? systemTitleBar,
|
||||
required CloseBehavior? closeBehavior,
|
||||
required SpotubeColor? accentColorScheme,
|
||||
required LayoutMode? layoutMode,
|
||||
required Locale? locale,
|
||||
required Market? recommendationMarket,
|
||||
required SearchMode? searchMode,
|
||||
required String? downloadLocation,
|
||||
required String? pipedInstance,
|
||||
required ThemeMode? themeMode,
|
||||
required AudioSource? audioSource,
|
||||
required SourceCodecs? streamMusicCodec,
|
||||
required SourceCodecs? downloadMusicCodec,
|
||||
}) : accentColorScheme =
|
||||
accentColorScheme ?? const SpotubeColor(0xFF2196F3, name: "Blue"),
|
||||
albumColorSync = albumColorSync ?? true,
|
||||
amoledDarkTheme = amoledDarkTheme ?? false,
|
||||
audioQuality = audioQuality ?? SourceQualities.high,
|
||||
checkUpdate = checkUpdate ?? true,
|
||||
closeBehavior = closeBehavior ?? CloseBehavior.close,
|
||||
downloadLocation = downloadLocation ?? "",
|
||||
downloadMusicCodec = downloadMusicCodec ?? SourceCodecs.m4a,
|
||||
layoutMode = layoutMode ?? LayoutMode.adaptive,
|
||||
locale = locale ?? const Locale("system", "system"),
|
||||
normalizeAudio = normalizeAudio ?? true,
|
||||
pipedInstance = pipedInstance ?? "https://pipedapi.kavin.rocks",
|
||||
recommendationMarket = recommendationMarket ?? Market.US,
|
||||
searchMode = searchMode ?? SearchMode.youtube,
|
||||
showSystemTrayIcon = showSystemTrayIcon ?? true,
|
||||
skipNonMusic = skipNonMusic ?? true,
|
||||
streamMusicCodec = streamMusicCodec ?? SourceCodecs.weba,
|
||||
systemTitleBar = systemTitleBar ?? false,
|
||||
themeMode = themeMode ?? ThemeMode.system,
|
||||
audioSource = audioSource ?? AudioSource.youtube {
|
||||
if (downloadLocation == null) {
|
||||
_getDefaultDownloadDirectory().then(
|
||||
(value) {
|
||||
downloadLocation = value;
|
||||
},
|
||||
(value) => this.downloadLocation = value,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void reset() {
|
||||
setRecommendationMarket(Market.US);
|
||||
setThemeMode(ThemeMode.system);
|
||||
setLayoutMode(LayoutMode.adaptive);
|
||||
setAlbumColorSync(true);
|
||||
setCheckUpdate(true);
|
||||
setAudioQuality(SourceQualities.high);
|
||||
setDownloadLocation("");
|
||||
setCloseBehavior(CloseBehavior.close);
|
||||
setShowSystemTrayIcon(true);
|
||||
setLocale(const Locale("system", "system"));
|
||||
setPipedInstance("https://pipedapi.kavin.rocks");
|
||||
setSearchMode(SearchMode.youtube);
|
||||
setSkipNonMusic(true);
|
||||
setAudioSource(AudioSource.youtube);
|
||||
setSystemTitleBar(false);
|
||||
setAmoledDarkTheme(false);
|
||||
setNormalizeAudio(true);
|
||||
setAccentColorScheme(SpotubeColor(Colors.blue.value, name: "Blue"));
|
||||
setStreamMusicCodec(SourceCodecs.weba);
|
||||
setDownloadMusicCodec(SourceCodecs.m4a);
|
||||
factory UserPreferences.withDefaults() {
|
||||
return UserPreferences(
|
||||
audioQuality: null,
|
||||
albumColorSync: null,
|
||||
amoledDarkTheme: null,
|
||||
checkUpdate: null,
|
||||
normalizeAudio: null,
|
||||
showSystemTrayIcon: null,
|
||||
skipNonMusic: null,
|
||||
systemTitleBar: null,
|
||||
closeBehavior: null,
|
||||
accentColorScheme: null,
|
||||
layoutMode: null,
|
||||
locale: null,
|
||||
recommendationMarket: null,
|
||||
searchMode: null,
|
||||
downloadLocation: null,
|
||||
pipedInstance: null,
|
||||
themeMode: null,
|
||||
audioSource: null,
|
||||
streamMusicCodec: null,
|
||||
downloadMusicCodec: null,
|
||||
);
|
||||
}
|
||||
|
||||
void setStreamMusicCodec(SourceCodecs codec) {
|
||||
streamMusicCodec = codec;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setDownloadMusicCodec(SourceCodecs codec) {
|
||||
downloadMusicCodec = codec;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setThemeMode(ThemeMode mode) {
|
||||
themeMode = mode;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setRecommendationMarket(Market country) {
|
||||
recommendationMarket = country;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setAccentColorScheme(SpotubeColor color) {
|
||||
accentColorScheme = color;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setAlbumColorSync(bool sync) {
|
||||
albumColorSync = sync;
|
||||
if (!sync) {
|
||||
ref.read(paletteProvider.notifier).state = null;
|
||||
} else {
|
||||
ref.read(ProxyPlaylistNotifier.notifier).updatePalette();
|
||||
}
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setCheckUpdate(bool check) {
|
||||
checkUpdate = check;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setAudioQuality(SourceQualities quality) {
|
||||
audioQuality = quality;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setDownloadLocation(String downloadDir) {
|
||||
if (downloadDir.isEmpty) return;
|
||||
downloadLocation = downloadDir;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setLayoutMode(LayoutMode mode) {
|
||||
layoutMode = mode;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setCloseBehavior(CloseBehavior behavior) {
|
||||
closeBehavior = behavior;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setShowSystemTrayIcon(bool show) {
|
||||
showSystemTrayIcon = show;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setLocale(Locale locale) {
|
||||
this.locale = locale;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setPipedInstance(String instance) {
|
||||
pipedInstance = instance;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setSearchMode(SearchMode mode) {
|
||||
searchMode = mode;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setSkipNonMusic(bool skip) {
|
||||
skipNonMusic = skip;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setAudioSource(AudioSource type) {
|
||||
audioSource = type;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setSystemTitleBar(bool isSystemTitleBar) {
|
||||
systemTitleBar = isSystemTitleBar;
|
||||
if (DesktopTools.platform.isDesktop) {
|
||||
DesktopTools.window.setTitleBarStyle(
|
||||
systemTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden,
|
||||
);
|
||||
}
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setAmoledDarkTheme(bool isAmoled) {
|
||||
amoledDarkTheme = isAmoled;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setNormalizeAudio(bool normalize) {
|
||||
normalizeAudio = normalize;
|
||||
audioPlayer.setAudioNormalization(normalize);
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
Future<String> _getDefaultDownloadDirectory() async {
|
||||
static Future<String> _getDefaultDownloadDirectory() async {
|
||||
if (kIsAndroid) return "/storage/emulated/0/Download/Spotube";
|
||||
|
||||
if (kIsMacOS) {
|
||||
@ -289,102 +171,71 @@ class UserPreferences extends PersistedChangeNotifier {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> loadFromLocal(Map<String, dynamic> map) async {
|
||||
recommendationMarket = Market.values.firstWhere(
|
||||
(market) =>
|
||||
market.name == (map["recommendationMarket"] ?? recommendationMarket),
|
||||
orElse: () => Market.US,
|
||||
);
|
||||
checkUpdate = map["checkUpdate"] ?? checkUpdate;
|
||||
|
||||
themeMode = ThemeMode.values[map["themeMode"] ?? 0];
|
||||
accentColorScheme = map["accentColorScheme"] != null
|
||||
? SpotubeColor.fromString(map["accentColorScheme"])
|
||||
: accentColorScheme;
|
||||
albumColorSync = map["albumColorSync"] ?? albumColorSync;
|
||||
audioQuality = map["audioQuality"] != null
|
||||
? SourceQualities.values[map["audioQuality"]]
|
||||
: audioQuality;
|
||||
|
||||
if (!kIsWeb) {
|
||||
downloadLocation =
|
||||
map["downloadLocation"] ?? await _getDefaultDownloadDirectory();
|
||||
static Future<UserPreferences> fromJson(Map<String, dynamic> json) async {
|
||||
final systemTitleBar = json["systemTitleBar"] ?? false;
|
||||
if (DesktopTools.platform.isDesktop) {
|
||||
await DesktopTools.window.setTitleBarStyle(
|
||||
systemTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden,
|
||||
);
|
||||
}
|
||||
|
||||
layoutMode = LayoutMode.values.firstWhere(
|
||||
(mode) => mode.name == map["layoutMode"],
|
||||
orElse: () => kIsDesktop ? LayoutMode.extended : LayoutMode.compact,
|
||||
);
|
||||
|
||||
closeBehavior = map["closeBehavior"] != null
|
||||
? CloseBehavior.values[map["closeBehavior"]]
|
||||
: closeBehavior;
|
||||
|
||||
showSystemTrayIcon = map["showSystemTrayIcon"] ?? showSystemTrayIcon;
|
||||
|
||||
final localeMap = map["locale"] != null ? jsonDecode(map["locale"]) : null;
|
||||
locale =
|
||||
localeMap != null ? Locale(localeMap?["lc"], localeMap?["cc"]) : locale;
|
||||
|
||||
pipedInstance = map["pipedInstance"] ?? pipedInstance;
|
||||
|
||||
searchMode = SearchMode.values.firstWhere(
|
||||
(mode) => mode.name == map["searchMode"],
|
||||
orElse: () => SearchMode.youtube,
|
||||
);
|
||||
|
||||
skipNonMusic = map["skipNonMusic"] ?? skipNonMusic;
|
||||
|
||||
audioSource = AudioSource.values.firstWhere(
|
||||
(type) => type.name == map["youtubeApiType"],
|
||||
orElse: () => AudioSource.youtube,
|
||||
);
|
||||
|
||||
systemTitleBar = map["systemTitleBar"] ?? systemTitleBar;
|
||||
// updates the title bar
|
||||
setSystemTitleBar(systemTitleBar);
|
||||
|
||||
amoledDarkTheme = map["amoledDarkTheme"] ?? amoledDarkTheme;
|
||||
|
||||
normalizeAudio = map["normalizeAudio"] ?? normalizeAudio;
|
||||
final normalizeAudio = json["normalizeAudio"] ?? true;
|
||||
audioPlayer.setAudioNormalization(normalizeAudio);
|
||||
|
||||
streamMusicCodec = SourceCodecs.values.firstWhere(
|
||||
(codec) => codec.name == map["streamMusicCodec"],
|
||||
orElse: () => SourceCodecs.weba,
|
||||
);
|
||||
final Map<String, dynamic>? localeMap =
|
||||
json["locale"] == null ? null : jsonDecode(json["locale"]);
|
||||
|
||||
downloadMusicCodec = SourceCodecs.values.firstWhere(
|
||||
(codec) => codec.name == map["downloadMusicCodec"],
|
||||
orElse: () => SourceCodecs.m4a,
|
||||
return UserPreferences(
|
||||
accentColorScheme: json["accentColorScheme"] == null
|
||||
? null
|
||||
: SpotubeColor.fromString(json["accentColorScheme"]),
|
||||
albumColorSync: json["albumColorSync"],
|
||||
amoledDarkTheme: json["amoledDarkTheme"],
|
||||
audioQuality: SourceQualities.values[json["audioQuality"]],
|
||||
checkUpdate: json["checkUpdate"],
|
||||
closeBehavior: CloseBehavior.values[json["closeBehavior"]],
|
||||
downloadLocation:
|
||||
json["downloadLocation"] ?? await _getDefaultDownloadDirectory(),
|
||||
downloadMusicCodec: SourceCodecs.values[json["downloadMusicCodec"]],
|
||||
layoutMode: LayoutMode.values[json["layoutMode"]],
|
||||
locale:
|
||||
localeMap == null ? null : Locale(localeMap["lc"], localeMap["cc"]),
|
||||
normalizeAudio: json["normalizeAudio"],
|
||||
pipedInstance: json["pipedInstance"],
|
||||
recommendationMarket: Market.values[json["recommendationMarket"]],
|
||||
searchMode: SearchMode.values[json["searchMode"]],
|
||||
showSystemTrayIcon: json["showSystemTrayIcon"],
|
||||
skipNonMusic: json["skipNonMusic"],
|
||||
streamMusicCodec: SourceCodecs.values[json["streamMusicCodec"]],
|
||||
systemTitleBar: json["systemTitleBar"],
|
||||
themeMode: ThemeMode.values[json["themeMode"]],
|
||||
audioSource: AudioSource.values[json["audioSource"]],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<Map<String, dynamic>> toMap() {
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
"recommendationMarket": recommendationMarket.name,
|
||||
"recommendationMarket": recommendationMarket.index,
|
||||
"themeMode": themeMode.index,
|
||||
"accentColorScheme": accentColorScheme.toString(),
|
||||
"albumColorSync": albumColorSync,
|
||||
"checkUpdate": checkUpdate,
|
||||
"audioQuality": audioQuality.index,
|
||||
"downloadLocation": downloadLocation,
|
||||
"layoutMode": layoutMode.name,
|
||||
"layoutMode": layoutMode.index,
|
||||
"closeBehavior": closeBehavior.index,
|
||||
"showSystemTrayIcon": showSystemTrayIcon,
|
||||
"locale":
|
||||
jsonEncode({"lc": locale.languageCode, "cc": locale.countryCode}),
|
||||
"pipedInstance": pipedInstance,
|
||||
"searchMode": searchMode.name,
|
||||
"searchMode": searchMode.index,
|
||||
"skipNonMusic": skipNonMusic,
|
||||
"youtubeApiType": audioSource.name,
|
||||
"audioSource": audioSource.index,
|
||||
'systemTitleBar': systemTitleBar,
|
||||
"amoledDarkTheme": amoledDarkTheme,
|
||||
"normalizeAudio": normalizeAudio,
|
||||
"streamMusicCodec": streamMusicCodec.name,
|
||||
"downloadMusicCodec": downloadMusicCodec.name,
|
||||
"streamMusicCodec": streamMusicCodec.index,
|
||||
"downloadMusicCodec": downloadMusicCodec.index,
|
||||
};
|
||||
}
|
||||
|
||||
@ -402,12 +253,16 @@ class UserPreferences extends PersistedChangeNotifier {
|
||||
String? pipedInstance,
|
||||
SearchMode? searchMode,
|
||||
bool? skipNonMusic,
|
||||
AudioSource? youtubeApiType,
|
||||
AudioSource? audioSource,
|
||||
Market? recommendationMarket,
|
||||
bool? saveTrackLyrics,
|
||||
bool? amoledDarkTheme,
|
||||
bool? normalizeAudio,
|
||||
SourceCodecs? downloadMusicCodec,
|
||||
SourceCodecs? streamMusicCodec,
|
||||
bool? systemTitleBar,
|
||||
}) {
|
||||
return UserPreferences(
|
||||
ref,
|
||||
themeMode: themeMode ?? this.themeMode,
|
||||
accentColorScheme: accentColorScheme ?? this.accentColorScheme,
|
||||
albumColorSync: albumColorSync ?? this.albumColorSync,
|
||||
@ -421,12 +276,132 @@ class UserPreferences extends PersistedChangeNotifier {
|
||||
pipedInstance: pipedInstance ?? this.pipedInstance,
|
||||
searchMode: searchMode ?? this.searchMode,
|
||||
skipNonMusic: skipNonMusic ?? this.skipNonMusic,
|
||||
audioSource: youtubeApiType ?? this.audioSource,
|
||||
audioSource: audioSource ?? this.audioSource,
|
||||
recommendationMarket: recommendationMarket ?? this.recommendationMarket,
|
||||
amoledDarkTheme: amoledDarkTheme ?? this.amoledDarkTheme,
|
||||
downloadMusicCodec: downloadMusicCodec ?? this.downloadMusicCodec,
|
||||
normalizeAudio: normalizeAudio ?? this.normalizeAudio,
|
||||
streamMusicCodec: streamMusicCodec ?? this.streamMusicCodec,
|
||||
systemTitleBar: systemTitleBar ?? this.systemTitleBar,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final userPreferencesProvider = ChangeNotifierProvider(
|
||||
(ref) => UserPreferences(ref),
|
||||
class UserPreferencesNotifier extends PersistedStateNotifier<UserPreferences> {
|
||||
final Ref ref;
|
||||
|
||||
UserPreferencesNotifier(this.ref)
|
||||
: super(UserPreferences.withDefaults(), "preferences");
|
||||
|
||||
void reset() {
|
||||
state = UserPreferences.withDefaults();
|
||||
}
|
||||
|
||||
void setStreamMusicCodec(SourceCodecs codec) {
|
||||
state = state.copyWith(streamMusicCodec: codec);
|
||||
}
|
||||
|
||||
void setDownloadMusicCodec(SourceCodecs codec) {
|
||||
state = state.copyWith(downloadMusicCodec: codec);
|
||||
}
|
||||
|
||||
void setThemeMode(ThemeMode mode) {
|
||||
state = state.copyWith(themeMode: mode);
|
||||
}
|
||||
|
||||
void setRecommendationMarket(Market country) {
|
||||
state = state.copyWith(recommendationMarket: country);
|
||||
}
|
||||
|
||||
void setAccentColorScheme(SpotubeColor color) {
|
||||
state = state.copyWith(accentColorScheme: color);
|
||||
}
|
||||
|
||||
void setAlbumColorSync(bool sync) {
|
||||
state = state.copyWith(albumColorSync: sync);
|
||||
|
||||
if (!sync) {
|
||||
ref.read(paletteProvider.notifier).state = null;
|
||||
} else {
|
||||
ref.read(ProxyPlaylistNotifier.notifier).updatePalette();
|
||||
}
|
||||
}
|
||||
|
||||
void setCheckUpdate(bool check) {
|
||||
state = state.copyWith(checkUpdate: check);
|
||||
}
|
||||
|
||||
void setAudioQuality(SourceQualities quality) {
|
||||
state = state.copyWith(audioQuality: quality);
|
||||
}
|
||||
|
||||
void setDownloadLocation(String downloadDir) {
|
||||
if (downloadDir.isEmpty) return;
|
||||
state = state.copyWith(downloadLocation: downloadDir);
|
||||
}
|
||||
|
||||
void setLayoutMode(LayoutMode mode) {
|
||||
state = state.copyWith(layoutMode: mode);
|
||||
}
|
||||
|
||||
void setCloseBehavior(CloseBehavior behavior) {
|
||||
state = state.copyWith(closeBehavior: behavior);
|
||||
}
|
||||
|
||||
void setShowSystemTrayIcon(bool show) {
|
||||
state = state.copyWith(showSystemTrayIcon: show);
|
||||
}
|
||||
|
||||
void setLocale(Locale locale) {
|
||||
state = state.copyWith(locale: locale);
|
||||
}
|
||||
|
||||
void setPipedInstance(String instance) {
|
||||
state = state.copyWith(pipedInstance: instance);
|
||||
}
|
||||
|
||||
void setSearchMode(SearchMode mode) {
|
||||
state = state.copyWith(searchMode: mode);
|
||||
}
|
||||
|
||||
void setSkipNonMusic(bool skip) {
|
||||
state = state.copyWith(skipNonMusic: skip);
|
||||
}
|
||||
|
||||
void setAudioSource(AudioSource type) {
|
||||
state = state.copyWith(audioSource: type);
|
||||
}
|
||||
|
||||
void setSystemTitleBar(bool isSystemTitleBar) {
|
||||
state = state.copyWith(systemTitleBar: isSystemTitleBar);
|
||||
if (DesktopTools.platform.isDesktop) {
|
||||
DesktopTools.window.setTitleBarStyle(
|
||||
isSystemTitleBar ? TitleBarStyle.normal : TitleBarStyle.hidden,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void setAmoledDarkTheme(bool isAmoled) {
|
||||
state = state.copyWith(amoledDarkTheme: isAmoled);
|
||||
}
|
||||
|
||||
void setNormalizeAudio(bool normalize) {
|
||||
state = state.copyWith(normalizeAudio: normalize);
|
||||
audioPlayer.setAudioNormalization(normalize);
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<UserPreferences> fromJson(Map<String, dynamic> json) {
|
||||
return UserPreferences.fromJson(json);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return state.toJson();
|
||||
}
|
||||
}
|
||||
|
||||
final userPreferencesProvider =
|
||||
StateNotifierProvider<UserPreferencesNotifier, UserPreferences>(
|
||||
(ref) => UserPreferencesNotifier(ref),
|
||||
);
|
||||
|
||||
@ -473,6 +473,15 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
draggable_scrollbar:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: cfd570035bf393de541d32e9b28808b5d7e602df
|
||||
resolved-ref: cfd570035bf393de541d32e9b28808b5d7e602df
|
||||
url: "https://github.com/thielepaul/flutter-draggable-scrollbar.git"
|
||||
source: git
|
||||
version: "0.1.0"
|
||||
duration:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@ -109,6 +109,10 @@ dependencies:
|
||||
jiosaavn:
|
||||
git:
|
||||
url: https://github.com/KRTirtho/jiosaavn.git
|
||||
draggable_scrollbar:
|
||||
git:
|
||||
url: https://github.com/thielepaul/flutter-draggable-scrollbar.git
|
||||
ref: cfd570035bf393de541d32e9b28808b5d7e602df
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.3.2
|
||||
|
||||
Loading…
Reference in New Issue
Block a user