mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
chore: fix width of scrollbar & non-interactive scrollbar in android
This commit is contained in:
parent
5bb8231782
commit
a3250882df
@ -7,6 +7,7 @@ import 'package:spotify/spotify.dart';
|
|||||||
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/album/album_card.dart';
|
import 'package:spotube/components/album/album_card.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/shimmers/shimmer_playbutton_card.dart';
|
||||||
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
||||||
import 'package:spotube/components/shared/waypoint.dart';
|
import 'package:spotube/components/shared/waypoint.dart';
|
||||||
@ -70,39 +71,42 @@ class UserAlbums extends HookConsumerWidget {
|
|||||||
child: SearchBar(
|
child: SearchBar(
|
||||||
onChanged: (value) => searchText.value = value,
|
onChanged: (value) => searchText.value = value,
|
||||||
leading: const Icon(SpotubeIcons.filter),
|
leading: const Icon(SpotubeIcons.filter),
|
||||||
hintText: context.l10n.filter_artist,
|
hintText: context.l10n.filter_albums,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: SizedBox.expand(
|
body: SizedBox.expand(
|
||||||
child: SingleChildScrollView(
|
child: InterScrollbar(
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
controller: controller,
|
controller: controller,
|
||||||
child: Wrap(
|
child: SingleChildScrollView(
|
||||||
runSpacing: 20,
|
padding: const EdgeInsets.all(8.0),
|
||||||
alignment: WrapAlignment.center,
|
controller: controller,
|
||||||
runAlignment: WrapAlignment.center,
|
child: Wrap(
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
runSpacing: 20,
|
||||||
children: [
|
alignment: WrapAlignment.center,
|
||||||
if (albums.isEmpty)
|
runAlignment: WrapAlignment.center,
|
||||||
Container(
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
alignment: Alignment.topLeft,
|
children: [
|
||||||
padding: const EdgeInsets.all(16.0),
|
if (albums.isEmpty)
|
||||||
child: const ShimmerPlaybuttonCard(count: 4),
|
Container(
|
||||||
),
|
alignment: Alignment.topLeft,
|
||||||
for (final album in albums)
|
padding: const EdgeInsets.all(16.0),
|
||||||
AlbumCard(
|
child: const ShimmerPlaybuttonCard(count: 4),
|
||||||
TypeConversionUtils.simpleAlbum_X_Album(album),
|
),
|
||||||
),
|
for (final album in albums)
|
||||||
if (albumsQuery.hasNextPage)
|
AlbumCard(
|
||||||
Waypoint(
|
TypeConversionUtils.simpleAlbum_X_Album(album),
|
||||||
controller: controller,
|
),
|
||||||
isGrid: true,
|
if (albumsQuery.hasNextPage)
|
||||||
onTouchEdge: albumsQuery.fetchNext,
|
Waypoint(
|
||||||
child: const ShimmerPlaybuttonCard(count: 1),
|
controller: controller,
|
||||||
)
|
isGrid: true,
|
||||||
],
|
onTouchEdge: albumsQuery.fetchNext,
|
||||||
|
child: const ShimmerPlaybuttonCard(count: 1),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -7,6 +7,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
||||||
import 'package:spotube/components/artist/artist_card.dart';
|
import 'package:spotube/components/artist/artist_card.dart';
|
||||||
|
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/provider/authentication_provider.dart';
|
import 'package:spotube/provider/authentication_provider.dart';
|
||||||
import 'package:spotube/services/queries/queries.dart';
|
import 'package:spotube/services/queries/queries.dart';
|
||||||
@ -78,18 +79,21 @@ class UserArtists extends HookConsumerWidget {
|
|||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await artistQuery.refresh();
|
await artistQuery.refresh();
|
||||||
},
|
},
|
||||||
child: SingleChildScrollView(
|
child: InterScrollbar(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
child: SizedBox(
|
child: SingleChildScrollView(
|
||||||
width: double.infinity,
|
controller: controller,
|
||||||
child: SafeArea(
|
child: SizedBox(
|
||||||
child: Center(
|
width: double.infinity,
|
||||||
child: Wrap(
|
child: SafeArea(
|
||||||
spacing: 15,
|
child: Center(
|
||||||
runSpacing: 5,
|
child: Wrap(
|
||||||
children: filteredArtists
|
spacing: 15,
|
||||||
.mapIndexed((index, artist) => ArtistCard(artist))
|
runSpacing: 5,
|
||||||
.toList(),
|
children: filteredArtists
|
||||||
|
.mapIndexed((index, artist) => ArtistCard(artist))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -17,6 +17,7 @@ import 'package:permission_handler/permission_handler.dart';
|
|||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/expandable_search/expandable_search.dart';
|
import 'package:spotube/components/shared/expandable_search/expandable_search.dart';
|
||||||
|
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
||||||
import 'package:spotube/components/shared/shimmers/shimmer_track_tile.dart';
|
import 'package:spotube/components/shared/shimmers/shimmer_track_tile.dart';
|
||||||
import 'package:spotube/components/shared/sort_tracks_dropdown.dart';
|
import 'package:spotube/components/shared/sort_tracks_dropdown.dart';
|
||||||
import 'package:spotube/components/shared/track_table/track_tile.dart';
|
import 'package:spotube/components/shared/track_table/track_tile.dart';
|
||||||
@ -286,24 +287,26 @@ class UserLocalTracks extends HookConsumerWidget {
|
|||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
ref.refresh(localTracksProvider);
|
ref.refresh(localTracksProvider);
|
||||||
},
|
},
|
||||||
child: ListView.builder(
|
child: InterScrollbar(
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
child: ListView.builder(
|
||||||
itemCount: filteredTracks.length,
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
itemBuilder: (context, index) {
|
itemCount: filteredTracks.length,
|
||||||
final track = filteredTracks[index];
|
itemBuilder: (context, index) {
|
||||||
return TrackTile(
|
final track = filteredTracks[index];
|
||||||
index: index,
|
return TrackTile(
|
||||||
track: track,
|
index: index,
|
||||||
userPlaylist: false,
|
track: track,
|
||||||
onTap: () async {
|
userPlaylist: false,
|
||||||
await playLocalTracks(
|
onTap: () async {
|
||||||
ref,
|
await playLocalTracks(
|
||||||
sortedTracks,
|
ref,
|
||||||
currentTrack: track,
|
sortedTracks,
|
||||||
);
|
currentTrack: track,
|
||||||
},
|
);
|
||||||
);
|
},
|
||||||
},
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -8,6 +8,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/playlist/playlist_create_dialog.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/shimmers/shimmer_playbutton_card.dart';
|
||||||
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
import 'package:spotube/components/shared/fallbacks/anonymous_fallback.dart';
|
||||||
import 'package:spotube/components/playlist/playlist_card.dart';
|
import 'package:spotube/components/playlist/playlist_card.dart';
|
||||||
@ -79,59 +80,62 @@ class UserPlaylists extends HookConsumerWidget {
|
|||||||
|
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: playlistsQuery.refresh,
|
onRefresh: playlistsQuery.refresh,
|
||||||
child: SingleChildScrollView(
|
child: InterScrollbar(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
child: SingleChildScrollView(
|
||||||
child: Waypoint(
|
|
||||||
controller: controller,
|
controller: controller,
|
||||||
onTouchEdge: () {
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
if (playlistsQuery.hasNextPage) {
|
child: Waypoint(
|
||||||
playlistsQuery.fetchNext();
|
controller: controller,
|
||||||
}
|
onTouchEdge: () {
|
||||||
},
|
if (playlistsQuery.hasNextPage) {
|
||||||
child: SafeArea(
|
playlistsQuery.fetchNext();
|
||||||
child: Column(
|
}
|
||||||
children: [
|
},
|
||||||
Padding(
|
child: SafeArea(
|
||||||
padding: const EdgeInsets.all(10),
|
child: Column(
|
||||||
child: SearchBar(
|
children: [
|
||||||
onChanged: (value) => searchText.value = value,
|
Padding(
|
||||||
hintText: context.l10n.filter_playlists,
|
padding: const EdgeInsets.all(10),
|
||||||
leading: const Icon(SpotubeIcons.filter),
|
child: SearchBar(
|
||||||
|
onChanged: (value) => searchText.value = value,
|
||||||
|
hintText: context.l10n.filter_playlists,
|
||||||
|
leading: const Icon(SpotubeIcons.filter),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
AnimatedCrossFade(
|
||||||
AnimatedCrossFade(
|
duration: const Duration(milliseconds: 300),
|
||||||
duration: const Duration(milliseconds: 300),
|
crossFadeState: playlistsQuery.isLoadingPage ||
|
||||||
crossFadeState: playlistsQuery.isLoadingPage ||
|
!playlistsQuery.hasPageData
|
||||||
!playlistsQuery.hasPageData
|
? CrossFadeState.showFirst
|
||||||
? CrossFadeState.showFirst
|
: CrossFadeState.showSecond,
|
||||||
: CrossFadeState.showSecond,
|
firstChild:
|
||||||
firstChild:
|
const Center(child: ShimmerPlaybuttonCard(count: 7)),
|
||||||
const Center(child: ShimmerPlaybuttonCard(count: 7)),
|
secondChild: Wrap(
|
||||||
secondChild: Wrap(
|
runSpacing: 10,
|
||||||
runSpacing: 10,
|
alignment: WrapAlignment.center,
|
||||||
alignment: WrapAlignment.center,
|
children: [
|
||||||
children: [
|
Row(
|
||||||
Row(
|
children: [
|
||||||
children: [
|
const SizedBox(width: 10),
|
||||||
const SizedBox(width: 10),
|
const PlaylistCreateDialogButton(),
|
||||||
const PlaylistCreateDialogButton(),
|
const SizedBox(width: 10),
|
||||||
const SizedBox(width: 10),
|
ElevatedButton.icon(
|
||||||
ElevatedButton.icon(
|
icon: const Icon(SpotubeIcons.magic),
|
||||||
icon: const Icon(SpotubeIcons.magic),
|
label: Text(context.l10n.generate_playlist),
|
||||||
label: Text(context.l10n.generate_playlist),
|
onPressed: () {
|
||||||
onPressed: () {
|
GoRouter.of(context).push("/library/generate");
|
||||||
GoRouter.of(context).push("/library/generate");
|
},
|
||||||
},
|
),
|
||||||
),
|
const SizedBox(width: 10),
|
||||||
const SizedBox(width: 10),
|
],
|
||||||
],
|
),
|
||||||
),
|
...playlists.map((playlist) => PlaylistCard(playlist))
|
||||||
...playlists.map((playlist) => PlaylistCard(playlist))
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -10,6 +10,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:scroll_to_index/scroll_to_index.dart';
|
import 'package:scroll_to_index/scroll_to_index.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/shared/fallbacks/not_found.dart';
|
import 'package:spotube/components/shared/fallbacks/not_found.dart';
|
||||||
|
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
||||||
import 'package:spotube/components/shared/track_table/track_tile.dart';
|
import 'package:spotube/components/shared/track_table/track_tile.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
@ -196,21 +197,55 @@ class PlayerQueue extends HookConsumerWidget {
|
|||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
if (!isSearching.value && searchText.value.isEmpty)
|
if (!isSearching.value && searchText.value.isEmpty)
|
||||||
Flexible(
|
Flexible(
|
||||||
child: ReorderableListView.builder(
|
child: InterScrollbar(
|
||||||
onReorder: (oldIndex, newIndex) {
|
controller: controller,
|
||||||
playlistNotifier.moveTrack(oldIndex, newIndex);
|
child: ReorderableListView.builder(
|
||||||
},
|
onReorder: (oldIndex, newIndex) {
|
||||||
scrollController: controller,
|
playlistNotifier.moveTrack(oldIndex, newIndex);
|
||||||
itemCount: tracks.length,
|
},
|
||||||
shrinkWrap: true,
|
scrollController: controller,
|
||||||
buildDefaultDragHandles: false,
|
itemCount: tracks.length,
|
||||||
itemBuilder: (context, i) {
|
shrinkWrap: true,
|
||||||
final track = tracks.elementAt(i);
|
buildDefaultDragHandles: false,
|
||||||
return AutoScrollTag(
|
itemBuilder: (context, i) {
|
||||||
key: ValueKey(i),
|
final track = tracks.elementAt(i);
|
||||||
controller: controller,
|
return AutoScrollTag(
|
||||||
index: i,
|
key: ValueKey(i),
|
||||||
child: Padding(
|
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
|
||||||
|
Flexible(
|
||||||
|
child: InterScrollbar(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: filteredTracks.length,
|
||||||
|
itemBuilder: (context, i) {
|
||||||
|
final track = filteredTracks.elementAt(i);
|
||||||
|
return Padding(
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.symmetric(horizontal: 8.0),
|
const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: TrackTile(
|
child: TrackTile(
|
||||||
@ -222,38 +257,10 @@ class PlayerQueue extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
await playlistNotifier.jumpToTrack(track);
|
await playlistNotifier.jumpToTrack(track);
|
||||||
},
|
},
|
||||||
leadingActions: [
|
|
||||||
ReorderableDragStartListener(
|
|
||||||
index: i,
|
|
||||||
child: const Icon(SpotubeIcons.dragHandle),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
Flexible(
|
|
||||||
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);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
69
lib/components/shared/inter_scrollbar/inter_scrollbar.dart
Normal file
69
lib/components/shared/inter_scrollbar/inter_scrollbar.dart
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
const InterScrollbar({
|
||||||
|
super.key,
|
||||||
|
required this.child,
|
||||||
|
this.controller,
|
||||||
|
this.thumbVisibility,
|
||||||
|
this.trackVisibility,
|
||||||
|
this.thickness,
|
||||||
|
this.radius,
|
||||||
|
this.notificationPredicate,
|
||||||
|
this.interactive,
|
||||||
|
this.scrollbarOrientation,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
|
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/playlist/playlist_create_dialog.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_track_tile.dart';
|
import 'package:spotube/components/shared/shimmers/shimmer_track_tile.dart';
|
||||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
||||||
import 'package:spotube/components/shared/track_table/track_collection_view/track_collection_heading.dart';
|
import 'package:spotube/components/shared/track_table/track_collection_view/track_collection_heading.dart';
|
||||||
@ -139,131 +140,134 @@ class TrackCollectionView<T> extends HookConsumerWidget {
|
|||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await tracksSnapshot.refresh();
|
await tracksSnapshot.refresh();
|
||||||
},
|
},
|
||||||
child: CustomScrollView(
|
child: InterScrollbar(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
child: CustomScrollView(
|
||||||
slivers: [
|
controller: controller,
|
||||||
SliverAppBar(
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
actions: [
|
slivers: [
|
||||||
AnimatedScale(
|
SliverAppBar(
|
||||||
duration: const Duration(milliseconds: 200),
|
actions: [
|
||||||
scale: collapsed.value ? 1 : 0,
|
AnimatedScale(
|
||||||
child: Row(
|
duration: const Duration(milliseconds: 200),
|
||||||
mainAxisSize: MainAxisSize.min,
|
scale: collapsed.value ? 1 : 0,
|
||||||
children: buttons,
|
child: Row(
|
||||||
),
|
mainAxisSize: MainAxisSize.min,
|
||||||
),
|
children: buttons,
|
||||||
AnimatedScale(
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
scale: collapsed.value ? 1 : 0,
|
|
||||||
child: IconButton(
|
|
||||||
tooltip: context.l10n.shuffle,
|
|
||||||
icon: const Icon(SpotubeIcons.shuffle),
|
|
||||||
onPressed: playingState == PlayButtonState.playing
|
|
||||||
? null
|
|
||||||
: onShuffledPlay,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
AnimatedScale(
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
scale: collapsed.value ? 1 : 0,
|
|
||||||
child: ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
shape: const CircleBorder(),
|
|
||||||
backgroundColor: theme.colorScheme.inversePrimary,
|
|
||||||
),
|
),
|
||||||
onPressed: tracksSnapshot.data != null ? onPlay : null,
|
|
||||||
child: switch (playingState) {
|
|
||||||
PlayButtonState.playing =>
|
|
||||||
const Icon(SpotubeIcons.pause),
|
|
||||||
PlayButtonState.notPlaying =>
|
|
||||||
const Icon(SpotubeIcons.play),
|
|
||||||
PlayButtonState.loading => const SizedBox(
|
|
||||||
height: 20,
|
|
||||||
width: 20,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
strokeWidth: .7,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
AnimatedScale(
|
||||||
],
|
duration: const Duration(milliseconds: 200),
|
||||||
floating: false,
|
scale: collapsed.value ? 1 : 0,
|
||||||
pinned: true,
|
child: IconButton(
|
||||||
expandedHeight: 400,
|
tooltip: context.l10n.shuffle,
|
||||||
automaticallyImplyLeading: kIsMobile,
|
icon: const Icon(SpotubeIcons.shuffle),
|
||||||
leading:
|
onPressed: playingState == PlayButtonState.playing
|
||||||
kIsMobile ? const BackButton(color: Colors.white) : null,
|
? null
|
||||||
iconTheme: IconThemeData(color: color?.titleTextColor),
|
: onShuffledPlay,
|
||||||
primary: true,
|
),
|
||||||
backgroundColor: color?.color.withOpacity(.8),
|
),
|
||||||
title: collapsed.value
|
AnimatedScale(
|
||||||
? Text(
|
duration: const Duration(milliseconds: 200),
|
||||||
title,
|
scale: collapsed.value ? 1 : 0,
|
||||||
style: theme.textTheme.titleMedium!.copyWith(
|
child: ElevatedButton(
|
||||||
color: color?.titleTextColor,
|
style: ElevatedButton.styleFrom(
|
||||||
fontWeight: FontWeight.w600,
|
shape: const CircleBorder(),
|
||||||
|
backgroundColor: theme.colorScheme.inversePrimary,
|
||||||
),
|
),
|
||||||
)
|
onPressed: tracksSnapshot.data != null ? onPlay : null,
|
||||||
: null,
|
child: switch (playingState) {
|
||||||
centerTitle: true,
|
PlayButtonState.playing =>
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
const Icon(SpotubeIcons.pause),
|
||||||
background: TrackCollectionHeading<T>(
|
PlayButtonState.notPlaying =>
|
||||||
color: color,
|
const Icon(SpotubeIcons.play),
|
||||||
title: title,
|
PlayButtonState.loading => const SizedBox(
|
||||||
description: description,
|
height: 20,
|
||||||
titleImage: titleImage,
|
width: 20,
|
||||||
playingState: playingState,
|
child: CircularProgressIndicator(
|
||||||
onPlay: onPlay,
|
strokeWidth: .7,
|
||||||
onShuffledPlay: onShuffledPlay,
|
),
|
||||||
tracksSnapshot: tracksSnapshot,
|
),
|
||||||
buttons: buttons,
|
},
|
||||||
album: album,
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
floating: false,
|
||||||
|
pinned: true,
|
||||||
|
expandedHeight: 400,
|
||||||
|
automaticallyImplyLeading: kIsMobile,
|
||||||
|
leading:
|
||||||
|
kIsMobile ? const BackButton(color: Colors.white) : null,
|
||||||
|
iconTheme: IconThemeData(color: color?.titleTextColor),
|
||||||
|
primary: true,
|
||||||
|
backgroundColor: color?.color.withOpacity(.8),
|
||||||
|
title: collapsed.value
|
||||||
|
? Text(
|
||||||
|
title,
|
||||||
|
style: theme.textTheme.titleMedium!.copyWith(
|
||||||
|
color: color?.titleTextColor,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
centerTitle: true,
|
||||||
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
|
background: TrackCollectionHeading<T>(
|
||||||
|
color: color,
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
|
titleImage: titleImage,
|
||||||
|
playingState: playingState,
|
||||||
|
onPlay: onPlay,
|
||||||
|
onShuffledPlay: onShuffledPlay,
|
||||||
|
tracksSnapshot: tracksSnapshot,
|
||||||
|
buttons: buttons,
|
||||||
|
album: album,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
HookBuilder(
|
||||||
HookBuilder(
|
builder: (context) {
|
||||||
builder: (context) {
|
if (tracksSnapshot.isLoading || !tracksSnapshot.hasData) {
|
||||||
if (tracksSnapshot.isLoading || !tracksSnapshot.hasData) {
|
return const ShimmerTrackTile();
|
||||||
return const ShimmerTrackTile();
|
} else if (tracksSnapshot.hasError) {
|
||||||
} else if (tracksSnapshot.hasError) {
|
return SliverToBoxAdapter(
|
||||||
return SliverToBoxAdapter(
|
child: Text(
|
||||||
child: Text(
|
context.l10n.error(tracksSnapshot.error ?? ""),
|
||||||
context.l10n.error(tracksSnapshot.error ?? ""),
|
),
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return TracksTableView(
|
|
||||||
(tracksSnapshot.data ?? []).map(
|
|
||||||
(track) {
|
|
||||||
if (track is Track) {
|
|
||||||
return track;
|
|
||||||
} else {
|
|
||||||
return TypeConversionUtils.simpleTrack_X_Track(
|
|
||||||
track,
|
|
||||||
album!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
).toList(),
|
|
||||||
onTrackPlayButtonPressed: onPlay,
|
|
||||||
playlistId: id,
|
|
||||||
userPlaylist: isOwned,
|
|
||||||
onFiltering: () {
|
|
||||||
// scroll the flexible space
|
|
||||||
// to allow more space for search results
|
|
||||||
controller.animateTo(
|
|
||||||
330,
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
);
|
|
||||||
},
|
return TracksTableView(
|
||||||
)
|
(tracksSnapshot.data ?? []).map(
|
||||||
],
|
(track) {
|
||||||
|
if (track is Track) {
|
||||||
|
return track;
|
||||||
|
} else {
|
||||||
|
return TypeConversionUtils.simpleTrack_X_Track(
|
||||||
|
track,
|
||||||
|
album!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
).toList(),
|
||||||
|
onTrackPlayButtonPressed: onPlay,
|
||||||
|
playlistId: id,
|
||||||
|
userPlaylist: isOwned,
|
||||||
|
onFiltering: () {
|
||||||
|
// scroll the flexible space
|
||||||
|
// to allow more space for search results
|
||||||
|
controller.animateTo(
|
||||||
|
330,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import 'package:spotify/spotify.dart';
|
|||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/genre/category_card.dart';
|
import 'package:spotube/components/genre/category_card.dart';
|
||||||
import 'package:spotube/components/shared/expandable_search/expandable_search.dart';
|
import 'package:spotube/components/shared/expandable_search/expandable_search.dart';
|
||||||
|
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
||||||
import 'package:spotube/components/shared/shimmers/shimmer_categories.dart';
|
import 'package:spotube/components/shared/shimmers/shimmer_categories.dart';
|
||||||
import 'package:spotube/components/shared/waypoint.dart';
|
import 'package:spotube/components/shared/waypoint.dart';
|
||||||
|
|
||||||
@ -77,21 +78,24 @@ class GenrePage extends HookConsumerWidget {
|
|||||||
const ShimmerCategories()
|
const ShimmerCategories()
|
||||||
else
|
else
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ListView.builder(
|
child: InterScrollbar(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
itemCount: categories.length,
|
child: ListView.builder(
|
||||||
itemBuilder: (context, index) {
|
controller: scrollController,
|
||||||
return AnimatedCrossFade(
|
itemCount: categories.length,
|
||||||
crossFadeState: searchController.text.isEmpty &&
|
itemBuilder: (context, index) {
|
||||||
index == categories.length - 1 &&
|
return AnimatedCrossFade(
|
||||||
categoriesQuery.hasNextPage
|
crossFadeState: searchController.text.isEmpty &&
|
||||||
? CrossFadeState.showFirst
|
index == categories.length - 1 &&
|
||||||
: CrossFadeState.showSecond,
|
categoriesQuery.hasNextPage
|
||||||
duration: const Duration(milliseconds: 300),
|
? CrossFadeState.showFirst
|
||||||
firstChild: const ShimmerCategories(),
|
: CrossFadeState.showSecond,
|
||||||
secondChild: CategoryCard(categories[index]),
|
duration: const Duration(milliseconds: 300),
|
||||||
);
|
firstChild: const ShimmerCategories(),
|
||||||
},
|
secondChild: CategoryCard(categories[index]),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -6,6 +6,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/album/album_card.dart';
|
import 'package:spotube/components/album/album_card.dart';
|
||||||
import 'package:spotube/components/playlist/playlist_card.dart';
|
import 'package:spotube/components/playlist/playlist_card.dart';
|
||||||
|
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
||||||
import 'package:spotube/components/shared/shimmers/shimmer_categories.dart';
|
import 'package:spotube/components/shared/shimmers/shimmer_categories.dart';
|
||||||
import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
|
import 'package:spotube/components/shared/shimmers/shimmer_playbutton_card.dart';
|
||||||
import 'package:spotube/components/shared/waypoint.dart';
|
import 'package:spotube/components/shared/waypoint.dart';
|
||||||
@ -96,6 +97,7 @@ class PersonalizedPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
|
final controller = useScrollController();
|
||||||
final auth = ref.watch(AuthenticationNotifier.provider);
|
final auth = ref.watch(AuthenticationNotifier.provider);
|
||||||
final featuredPlaylistsQuery = useQueries.playlist.featured(ref);
|
final featuredPlaylistsQuery = useQueries.playlist.featured(ref);
|
||||||
final playlists = useMemoized(
|
final playlists = useMemoized(
|
||||||
@ -124,40 +126,46 @@ class PersonalizedPage extends HookConsumerWidget {
|
|||||||
[newReleases.pages],
|
[newReleases.pages],
|
||||||
);
|
);
|
||||||
|
|
||||||
return ListView(
|
return InterScrollbar(
|
||||||
children: [
|
controller: controller,
|
||||||
if (!featuredPlaylistsQuery.hasPageData)
|
child: ListView(
|
||||||
const ShimmerCategories()
|
controller: controller,
|
||||||
else
|
children: [
|
||||||
PersonalizedItemCard(
|
if (!featuredPlaylistsQuery.hasPageData)
|
||||||
playlists: playlists,
|
const ShimmerCategories()
|
||||||
title: context.l10n.featured,
|
else
|
||||||
hasNextPage: featuredPlaylistsQuery.hasNextPage,
|
PersonalizedItemCard(
|
||||||
onFetchMore: featuredPlaylistsQuery.fetchNext,
|
playlists: playlists,
|
||||||
),
|
title: context.l10n.featured,
|
||||||
if (auth != null && newReleases.hasPageData && userArtistsQuery.hasData)
|
hasNextPage: featuredPlaylistsQuery.hasNextPage,
|
||||||
PersonalizedItemCard(
|
onFetchMore: featuredPlaylistsQuery.fetchNext,
|
||||||
albums: albums,
|
),
|
||||||
title: context.l10n.new_releases,
|
if (auth != null &&
|
||||||
hasNextPage: newReleases.hasNextPage,
|
newReleases.hasPageData &&
|
||||||
onFetchMore: newReleases.fetchNext,
|
userArtistsQuery.hasData)
|
||||||
),
|
PersonalizedItemCard(
|
||||||
...?madeForUser.data?["content"]?["items"]?.map((item) {
|
albums: albums,
|
||||||
final playlists = item["content"]?["items"]
|
title: context.l10n.new_releases,
|
||||||
?.where((itemL2) => itemL2["type"] == "playlist")
|
hasNextPage: newReleases.hasNextPage,
|
||||||
.map((itemL2) => PlaylistSimple.fromJson(itemL2))
|
onFetchMore: newReleases.fetchNext,
|
||||||
.toList()
|
),
|
||||||
.cast<PlaylistSimple>() ??
|
...?madeForUser.data?["content"]?["items"]?.map((item) {
|
||||||
<PlaylistSimple>[];
|
final playlists = item["content"]?["items"]
|
||||||
if (playlists.isEmpty) return const SizedBox.shrink();
|
?.where((itemL2) => itemL2["type"] == "playlist")
|
||||||
return PersonalizedItemCard(
|
.map((itemL2) => PlaylistSimple.fromJson(itemL2))
|
||||||
playlists: playlists,
|
.toList()
|
||||||
title: item["name"] ?? "",
|
.cast<PlaylistSimple>() ??
|
||||||
hasNextPage: false,
|
<PlaylistSimple>[];
|
||||||
onFetchMore: () {},
|
if (playlists.isEmpty) return const SizedBox.shrink();
|
||||||
);
|
return PersonalizedItemCard(
|
||||||
})
|
playlists: playlists,
|
||||||
],
|
title: item["name"] ?? "",
|
||||||
|
hasNextPage: false,
|
||||||
|
onFetchMore: () {},
|
||||||
|
);
|
||||||
|
})
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import 'package:spotube/components/settings/color_scheme_picker_dialog.dart';
|
|||||||
import 'package:spotube/components/settings/section_card_with_heading.dart';
|
import 'package:spotube/components/settings/section_card_with_heading.dart';
|
||||||
import 'package:spotube/components/shared/adaptive/adaptive_list_tile.dart';
|
import 'package:spotube/components/shared/adaptive/adaptive_list_tile.dart';
|
||||||
import 'package:spotube/components/shared/adaptive/adaptive_select_tile.dart';
|
import 'package:spotube/components/shared/adaptive/adaptive_select_tile.dart';
|
||||||
|
import 'package:spotube/components/shared/inter_scrollbar/inter_scrollbar.dart';
|
||||||
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
import 'package:spotube/components/shared/page_window_title_bar.dart';
|
||||||
import 'package:spotube/collections/spotify_markets.dart';
|
import 'package:spotube/collections/spotify_markets.dart';
|
||||||
import 'package:spotube/extensions/constrains.dart';
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
@ -70,504 +71,508 @@ class SettingsPage extends HookConsumerWidget {
|
|||||||
Flexible(
|
Flexible(
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: const BoxConstraints(maxWidth: 1366),
|
constraints: const BoxConstraints(maxWidth: 1366),
|
||||||
child: ListView(
|
child: InterScrollbar(
|
||||||
children: [
|
child: ListView(
|
||||||
const SettingsAccountSection(),
|
children: [
|
||||||
SectionCardWithHeading(
|
const SettingsAccountSection(),
|
||||||
heading: context.l10n.language_region,
|
|
||||||
children: [
|
|
||||||
AdaptiveSelectTile<Locale>(
|
|
||||||
value: preferences.locale,
|
|
||||||
onChanged: (locale) {
|
|
||||||
if (locale == null) return;
|
|
||||||
preferences.setLocale(locale);
|
|
||||||
},
|
|
||||||
title: Text(context.l10n.language),
|
|
||||||
secondary: const Icon(SpotubeIcons.language),
|
|
||||||
options: [
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: const Locale("system", "system"),
|
|
||||||
child: Text(context.l10n.system_default),
|
|
||||||
),
|
|
||||||
for (final locale in L10n.all)
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: locale,
|
|
||||||
child: Builder(builder: (context) {
|
|
||||||
final isoCodeName =
|
|
||||||
LanguageLocals.getDisplayLanguage(
|
|
||||||
locale.languageCode,
|
|
||||||
);
|
|
||||||
return Text(
|
|
||||||
"${isoCodeName.name} (${isoCodeName.nativeName})",
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
AdaptiveSelectTile<Market>(
|
|
||||||
breakLayout: mediaQuery.lgAndUp,
|
|
||||||
secondary: const Icon(SpotubeIcons.shoppingBag),
|
|
||||||
title: Text(context.l10n.market_place_region),
|
|
||||||
subtitle: Text(context.l10n.recommendation_country),
|
|
||||||
value: preferences.recommendationMarket,
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value == null) return;
|
|
||||||
preferences.setRecommendationMarket(value);
|
|
||||||
},
|
|
||||||
options: spotifyMarkets
|
|
||||||
.map(
|
|
||||||
(country) => DropdownMenuItem(
|
|
||||||
value: country.$1,
|
|
||||||
child: Text(country.$2),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SectionCardWithHeading(
|
|
||||||
heading: context.l10n.appearance,
|
|
||||||
children: [
|
|
||||||
AdaptiveSelectTile<LayoutMode>(
|
|
||||||
secondary: const Icon(SpotubeIcons.dashboard),
|
|
||||||
title: Text(context.l10n.layout_mode),
|
|
||||||
subtitle: Text(context.l10n.override_layout_settings),
|
|
||||||
value: preferences.layoutMode,
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value != null) {
|
|
||||||
preferences.setLayoutMode(value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
options: [
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: LayoutMode.adaptive,
|
|
||||||
child: Text(context.l10n.adaptive),
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: LayoutMode.compact,
|
|
||||||
child: Text(context.l10n.compact),
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: LayoutMode.extended,
|
|
||||||
child: Text(context.l10n.extended),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
AdaptiveSelectTile<ThemeMode>(
|
|
||||||
secondary: const Icon(SpotubeIcons.darkMode),
|
|
||||||
title: Text(context.l10n.theme),
|
|
||||||
value: preferences.themeMode,
|
|
||||||
options: [
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: ThemeMode.dark,
|
|
||||||
child: Text(context.l10n.dark),
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: ThemeMode.light,
|
|
||||||
child: Text(context.l10n.light),
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: ThemeMode.system,
|
|
||||||
child: Text(context.l10n.system),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value != null) {
|
|
||||||
preferences.setThemeMode(value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
SwitchListTile(
|
|
||||||
secondary: const Icon(SpotubeIcons.amoled),
|
|
||||||
title: Text(context.l10n.use_amoled_mode),
|
|
||||||
subtitle: Text(context.l10n.pitch_dark_theme),
|
|
||||||
value: preferences.amoledDarkTheme,
|
|
||||||
onChanged: preferences.setAmoledDarkTheme,
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(SpotubeIcons.palette),
|
|
||||||
title: Text(context.l10n.accent_color),
|
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 15,
|
|
||||||
vertical: 5,
|
|
||||||
),
|
|
||||||
trailing: ColorTile.compact(
|
|
||||||
color: preferences.accentColorScheme,
|
|
||||||
onPressed: pickColorScheme(),
|
|
||||||
isActive: true,
|
|
||||||
),
|
|
||||||
onTap: pickColorScheme(),
|
|
||||||
),
|
|
||||||
SwitchListTile(
|
|
||||||
secondary: const Icon(SpotubeIcons.colorSync),
|
|
||||||
title: Text(context.l10n.sync_album_color),
|
|
||||||
subtitle:
|
|
||||||
Text(context.l10n.sync_album_color_description),
|
|
||||||
value: preferences.albumColorSync,
|
|
||||||
onChanged: preferences.setAlbumColorSync,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SectionCardWithHeading(
|
|
||||||
heading: context.l10n.playback,
|
|
||||||
children: [
|
|
||||||
AdaptiveSelectTile<AudioQuality>(
|
|
||||||
secondary: const Icon(SpotubeIcons.audioQuality),
|
|
||||||
title: Text(context.l10n.audio_quality),
|
|
||||||
value: preferences.audioQuality,
|
|
||||||
options: [
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: AudioQuality.high,
|
|
||||||
child: Text(context.l10n.high),
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: AudioQuality.low,
|
|
||||||
child: Text(context.l10n.low),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value != null) {
|
|
||||||
preferences.setAudioQuality(value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
AdaptiveSelectTile<YoutubeApiType>(
|
|
||||||
secondary: const Icon(SpotubeIcons.api),
|
|
||||||
title: Text(context.l10n.youtube_api_type),
|
|
||||||
value: preferences.youtubeApiType,
|
|
||||||
options: YoutubeApiType.values
|
|
||||||
.map((e) => DropdownMenuItem(
|
|
||||||
value: e,
|
|
||||||
child: Text(e.label),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value == null) return;
|
|
||||||
preferences.setYoutubeApiType(value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
child: preferences.youtubeApiType ==
|
|
||||||
YoutubeApiType.youtube
|
|
||||||
? const SizedBox.shrink()
|
|
||||||
: Consumer(builder: (context, ref, child) {
|
|
||||||
final instanceList =
|
|
||||||
ref.watch(pipedInstancesFutureProvider);
|
|
||||||
|
|
||||||
return instanceList.when(
|
|
||||||
data: (data) {
|
|
||||||
return AdaptiveSelectTile<String>(
|
|
||||||
secondary:
|
|
||||||
const Icon(SpotubeIcons.piped),
|
|
||||||
title:
|
|
||||||
Text(context.l10n.piped_instance),
|
|
||||||
subtitle: RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: context
|
|
||||||
.l10n.piped_description,
|
|
||||||
style:
|
|
||||||
theme.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
const TextSpan(text: "\n"),
|
|
||||||
TextSpan(
|
|
||||||
text:
|
|
||||||
context.l10n.piped_warning,
|
|
||||||
style:
|
|
||||||
theme.textTheme.labelMedium,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
value: preferences.pipedInstance,
|
|
||||||
showValueWhenUnfolded: false,
|
|
||||||
options: data
|
|
||||||
.sortedBy((e) => e.name)
|
|
||||||
.map(
|
|
||||||
(e) => DropdownMenuItem(
|
|
||||||
value: e.apiUrl,
|
|
||||||
child: RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text:
|
|
||||||
"${e.name.trim()}\n",
|
|
||||||
style: theme.textTheme
|
|
||||||
.labelLarge,
|
|
||||||
),
|
|
||||||
TextSpan(
|
|
||||||
text: e.locations
|
|
||||||
.map(
|
|
||||||
countryCodeToEmoji)
|
|
||||||
.join(""),
|
|
||||||
style: GoogleFonts
|
|
||||||
.notoColorEmoji(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value != null) {
|
|
||||||
preferences.setPipedInstance(value);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
loading: () => const Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
),
|
|
||||||
error: (error, stackTrace) =>
|
|
||||||
Text(error.toString()),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
child: preferences.youtubeApiType ==
|
|
||||||
YoutubeApiType.youtube
|
|
||||||
? const SizedBox.shrink()
|
|
||||||
: AdaptiveSelectTile<SearchMode>(
|
|
||||||
secondary: const Icon(SpotubeIcons.search),
|
|
||||||
title: Text(context.l10n.search_mode),
|
|
||||||
value: preferences.searchMode,
|
|
||||||
options: SearchMode.values
|
|
||||||
.map((e) => DropdownMenuItem(
|
|
||||||
value: e,
|
|
||||||
child: Text(e.label),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value == null) return;
|
|
||||||
preferences.setSearchMode(value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
child: preferences.searchMode ==
|
|
||||||
SearchMode.youtubeMusic &&
|
|
||||||
preferences.youtubeApiType ==
|
|
||||||
YoutubeApiType.piped
|
|
||||||
? const SizedBox.shrink()
|
|
||||||
: SwitchListTile(
|
|
||||||
secondary: const Icon(SpotubeIcons.skip),
|
|
||||||
title: Text(context.l10n.skip_non_music),
|
|
||||||
value: preferences.skipNonMusic,
|
|
||||||
onChanged: (state) {
|
|
||||||
preferences.setSkipNonMusic(state);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(SpotubeIcons.playlistRemove),
|
|
||||||
title: Text(context.l10n.blacklist),
|
|
||||||
subtitle: Text(context.l10n.blacklist_description),
|
|
||||||
onTap: () {
|
|
||||||
GoRouter.of(context).push("/settings/blacklist");
|
|
||||||
},
|
|
||||||
trailing: const Icon(SpotubeIcons.angleRight),
|
|
||||||
),
|
|
||||||
SwitchListTile(
|
|
||||||
secondary: const Icon(SpotubeIcons.normalize),
|
|
||||||
title: Text(context.l10n.normalize_audio),
|
|
||||||
subtitle: Text(context.l10n.blacklist_description),
|
|
||||||
value: preferences.normalizeAudio,
|
|
||||||
onChanged: preferences.setNormalizeAudio,
|
|
||||||
),
|
|
||||||
AdaptiveSelectTile<MusicCodec>(
|
|
||||||
secondary: const Icon(SpotubeIcons.stream),
|
|
||||||
title: Text(context.l10n.streaming_music_codec),
|
|
||||||
value: preferences.streamMusicCodec,
|
|
||||||
showValueWhenUnfolded: false,
|
|
||||||
options: MusicCodec.values
|
|
||||||
.map((e) => DropdownMenuItem(
|
|
||||||
value: e,
|
|
||||||
child: Text(
|
|
||||||
e.label,
|
|
||||||
style: theme.textTheme.labelMedium,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value == null) return;
|
|
||||||
preferences.setStreamMusicCodec(value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
AdaptiveSelectTile<MusicCodec>(
|
|
||||||
secondary: const Icon(SpotubeIcons.file),
|
|
||||||
title: Text(context.l10n.download_music_codec),
|
|
||||||
value: preferences.downloadMusicCodec,
|
|
||||||
showValueWhenUnfolded: false,
|
|
||||||
options: MusicCodec.values
|
|
||||||
.map((e) => DropdownMenuItem(
|
|
||||||
value: e,
|
|
||||||
child: Text(
|
|
||||||
e.label,
|
|
||||||
style: theme.textTheme.labelMedium,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
if (value == null) return;
|
|
||||||
preferences.setDownloadMusicCodec(value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SectionCardWithHeading(
|
|
||||||
heading: context.l10n.downloads,
|
|
||||||
children: [
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(SpotubeIcons.download),
|
|
||||||
title: Text(context.l10n.download_location),
|
|
||||||
subtitle: Text(preferences.downloadLocation),
|
|
||||||
trailing: FilledButton(
|
|
||||||
onPressed: pickDownloadLocation,
|
|
||||||
child: const Icon(SpotubeIcons.folder),
|
|
||||||
),
|
|
||||||
onTap: pickDownloadLocation,
|
|
||||||
),
|
|
||||||
SwitchListTile(
|
|
||||||
secondary: const Icon(SpotubeIcons.lyrics),
|
|
||||||
title: Text(context.l10n.download_lyrics),
|
|
||||||
value: preferences.saveTrackLyrics,
|
|
||||||
onChanged: (state) {
|
|
||||||
preferences.setSaveTrackLyrics(state);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (DesktopTools.platform.isDesktop)
|
|
||||||
SectionCardWithHeading(
|
SectionCardWithHeading(
|
||||||
heading: context.l10n.desktop,
|
heading: context.l10n.language_region,
|
||||||
children: [
|
children: [
|
||||||
AdaptiveSelectTile<CloseBehavior>(
|
AdaptiveSelectTile<Locale>(
|
||||||
secondary: const Icon(SpotubeIcons.close),
|
value: preferences.locale,
|
||||||
title: Text(context.l10n.close_behavior),
|
onChanged: (locale) {
|
||||||
value: preferences.closeBehavior,
|
if (locale == null) return;
|
||||||
|
preferences.setLocale(locale);
|
||||||
|
},
|
||||||
|
title: Text(context.l10n.language),
|
||||||
|
secondary: const Icon(SpotubeIcons.language),
|
||||||
options: [
|
options: [
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
value: CloseBehavior.close,
|
value: const Locale("system", "system"),
|
||||||
child: Text(context.l10n.close),
|
child: Text(context.l10n.system_default),
|
||||||
|
),
|
||||||
|
for (final locale in L10n.all)
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: locale,
|
||||||
|
child: Builder(builder: (context) {
|
||||||
|
final isoCodeName =
|
||||||
|
LanguageLocals.getDisplayLanguage(
|
||||||
|
locale.languageCode,
|
||||||
|
);
|
||||||
|
return Text(
|
||||||
|
"${isoCodeName.name} (${isoCodeName.nativeName})",
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
AdaptiveSelectTile<Market>(
|
||||||
|
breakLayout: mediaQuery.lgAndUp,
|
||||||
|
secondary: const Icon(SpotubeIcons.shoppingBag),
|
||||||
|
title: Text(context.l10n.market_place_region),
|
||||||
|
subtitle: Text(context.l10n.recommendation_country),
|
||||||
|
value: preferences.recommendationMarket,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
preferences.setRecommendationMarket(value);
|
||||||
|
},
|
||||||
|
options: spotifyMarkets
|
||||||
|
.map(
|
||||||
|
(country) => DropdownMenuItem(
|
||||||
|
value: country.$1,
|
||||||
|
child: Text(country.$2),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SectionCardWithHeading(
|
||||||
|
heading: context.l10n.appearance,
|
||||||
|
children: [
|
||||||
|
AdaptiveSelectTile<LayoutMode>(
|
||||||
|
secondary: const Icon(SpotubeIcons.dashboard),
|
||||||
|
title: Text(context.l10n.layout_mode),
|
||||||
|
subtitle:
|
||||||
|
Text(context.l10n.override_layout_settings),
|
||||||
|
value: preferences.layoutMode,
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
preferences.setLayoutMode(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: LayoutMode.adaptive,
|
||||||
|
child: Text(context.l10n.adaptive),
|
||||||
),
|
),
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
value: CloseBehavior.minimizeToTray,
|
value: LayoutMode.compact,
|
||||||
child: Text(context.l10n.minimize_to_tray),
|
child: Text(context.l10n.compact),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: LayoutMode.extended,
|
||||||
|
child: Text(context.l10n.extended),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
AdaptiveSelectTile<ThemeMode>(
|
||||||
|
secondary: const Icon(SpotubeIcons.darkMode),
|
||||||
|
title: Text(context.l10n.theme),
|
||||||
|
value: preferences.themeMode,
|
||||||
|
options: [
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: ThemeMode.dark,
|
||||||
|
child: Text(context.l10n.dark),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: ThemeMode.light,
|
||||||
|
child: Text(context.l10n.light),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: ThemeMode.system,
|
||||||
|
child: Text(context.l10n.system),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
preferences.setCloseBehavior(value);
|
preferences.setThemeMode(value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
secondary: const Icon(SpotubeIcons.tray),
|
secondary: const Icon(SpotubeIcons.amoled),
|
||||||
title: Text(context.l10n.show_tray_icon),
|
title: Text(context.l10n.use_amoled_mode),
|
||||||
value: preferences.showSystemTrayIcon,
|
subtitle: Text(context.l10n.pitch_dark_theme),
|
||||||
onChanged: preferences.setShowSystemTrayIcon,
|
value: preferences.amoledDarkTheme,
|
||||||
|
onChanged: preferences.setAmoledDarkTheme,
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(SpotubeIcons.palette),
|
||||||
|
title: Text(context.l10n.accent_color),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 15,
|
||||||
|
vertical: 5,
|
||||||
|
),
|
||||||
|
trailing: ColorTile.compact(
|
||||||
|
color: preferences.accentColorScheme,
|
||||||
|
onPressed: pickColorScheme(),
|
||||||
|
isActive: true,
|
||||||
|
),
|
||||||
|
onTap: pickColorScheme(),
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
secondary: const Icon(SpotubeIcons.window),
|
secondary: const Icon(SpotubeIcons.colorSync),
|
||||||
title: Text(context.l10n.use_system_title_bar),
|
title: Text(context.l10n.sync_album_color),
|
||||||
value: preferences.systemTitleBar,
|
subtitle:
|
||||||
onChanged: preferences.setSystemTitleBar,
|
Text(context.l10n.sync_album_color_description),
|
||||||
|
value: preferences.albumColorSync,
|
||||||
|
onChanged: preferences.setAlbumColorSync,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (!kIsWeb)
|
|
||||||
SectionCardWithHeading(
|
SectionCardWithHeading(
|
||||||
heading: context.l10n.developers,
|
heading: context.l10n.playback,
|
||||||
|
children: [
|
||||||
|
AdaptiveSelectTile<AudioQuality>(
|
||||||
|
secondary: const Icon(SpotubeIcons.audioQuality),
|
||||||
|
title: Text(context.l10n.audio_quality),
|
||||||
|
value: preferences.audioQuality,
|
||||||
|
options: [
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: AudioQuality.high,
|
||||||
|
child: Text(context.l10n.high),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: AudioQuality.low,
|
||||||
|
child: Text(context.l10n.low),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
preferences.setAudioQuality(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
AdaptiveSelectTile<YoutubeApiType>(
|
||||||
|
secondary: const Icon(SpotubeIcons.api),
|
||||||
|
title: Text(context.l10n.youtube_api_type),
|
||||||
|
value: preferences.youtubeApiType,
|
||||||
|
options: YoutubeApiType.values
|
||||||
|
.map((e) => DropdownMenuItem(
|
||||||
|
value: e,
|
||||||
|
child: Text(e.label),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
preferences.setYoutubeApiType(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: preferences.youtubeApiType ==
|
||||||
|
YoutubeApiType.youtube
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: Consumer(builder: (context, ref, child) {
|
||||||
|
final instanceList =
|
||||||
|
ref.watch(pipedInstancesFutureProvider);
|
||||||
|
|
||||||
|
return instanceList.when(
|
||||||
|
data: (data) {
|
||||||
|
return AdaptiveSelectTile<String>(
|
||||||
|
secondary:
|
||||||
|
const Icon(SpotubeIcons.piped),
|
||||||
|
title:
|
||||||
|
Text(context.l10n.piped_instance),
|
||||||
|
subtitle: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: context
|
||||||
|
.l10n.piped_description,
|
||||||
|
style: theme
|
||||||
|
.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
const TextSpan(text: "\n"),
|
||||||
|
TextSpan(
|
||||||
|
text: context
|
||||||
|
.l10n.piped_warning,
|
||||||
|
style: theme
|
||||||
|
.textTheme.labelMedium,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
value: preferences.pipedInstance,
|
||||||
|
showValueWhenUnfolded: false,
|
||||||
|
options: data
|
||||||
|
.sortedBy((e) => e.name)
|
||||||
|
.map(
|
||||||
|
(e) => DropdownMenuItem(
|
||||||
|
value: e.apiUrl,
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text:
|
||||||
|
"${e.name.trim()}\n",
|
||||||
|
style: theme.textTheme
|
||||||
|
.labelLarge,
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: e.locations
|
||||||
|
.map(
|
||||||
|
countryCodeToEmoji)
|
||||||
|
.join(""),
|
||||||
|
style: GoogleFonts
|
||||||
|
.notoColorEmoji(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
preferences
|
||||||
|
.setPipedInstance(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
error: (error, stackTrace) =>
|
||||||
|
Text(error.toString()),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: preferences.youtubeApiType ==
|
||||||
|
YoutubeApiType.youtube
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: AdaptiveSelectTile<SearchMode>(
|
||||||
|
secondary: const Icon(SpotubeIcons.search),
|
||||||
|
title: Text(context.l10n.search_mode),
|
||||||
|
value: preferences.searchMode,
|
||||||
|
options: SearchMode.values
|
||||||
|
.map((e) => DropdownMenuItem(
|
||||||
|
value: e,
|
||||||
|
child: Text(e.label),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
preferences.setSearchMode(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
child: preferences.searchMode ==
|
||||||
|
SearchMode.youtubeMusic &&
|
||||||
|
preferences.youtubeApiType ==
|
||||||
|
YoutubeApiType.piped
|
||||||
|
? const SizedBox.shrink()
|
||||||
|
: SwitchListTile(
|
||||||
|
secondary: const Icon(SpotubeIcons.skip),
|
||||||
|
title: Text(context.l10n.skip_non_music),
|
||||||
|
value: preferences.skipNonMusic,
|
||||||
|
onChanged: (state) {
|
||||||
|
preferences.setSkipNonMusic(state);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(SpotubeIcons.playlistRemove),
|
||||||
|
title: Text(context.l10n.blacklist),
|
||||||
|
subtitle: Text(context.l10n.blacklist_description),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).push("/settings/blacklist");
|
||||||
|
},
|
||||||
|
trailing: const Icon(SpotubeIcons.angleRight),
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
secondary: const Icon(SpotubeIcons.normalize),
|
||||||
|
title: Text(context.l10n.normalize_audio),
|
||||||
|
subtitle: Text(context.l10n.blacklist_description),
|
||||||
|
value: preferences.normalizeAudio,
|
||||||
|
onChanged: preferences.setNormalizeAudio,
|
||||||
|
),
|
||||||
|
AdaptiveSelectTile<MusicCodec>(
|
||||||
|
secondary: const Icon(SpotubeIcons.stream),
|
||||||
|
title: Text(context.l10n.streaming_music_codec),
|
||||||
|
value: preferences.streamMusicCodec,
|
||||||
|
showValueWhenUnfolded: false,
|
||||||
|
options: MusicCodec.values
|
||||||
|
.map((e) => DropdownMenuItem(
|
||||||
|
value: e,
|
||||||
|
child: Text(
|
||||||
|
e.label,
|
||||||
|
style: theme.textTheme.labelMedium,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
preferences.setStreamMusicCodec(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
AdaptiveSelectTile<MusicCodec>(
|
||||||
|
secondary: const Icon(SpotubeIcons.file),
|
||||||
|
title: Text(context.l10n.download_music_codec),
|
||||||
|
value: preferences.downloadMusicCodec,
|
||||||
|
showValueWhenUnfolded: false,
|
||||||
|
options: MusicCodec.values
|
||||||
|
.map((e) => DropdownMenuItem(
|
||||||
|
value: e,
|
||||||
|
child: Text(
|
||||||
|
e.label,
|
||||||
|
style: theme.textTheme.labelMedium,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value == null) return;
|
||||||
|
preferences.setDownloadMusicCodec(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SectionCardWithHeading(
|
||||||
|
heading: context.l10n.downloads,
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(SpotubeIcons.logs),
|
leading: const Icon(SpotubeIcons.download),
|
||||||
title: Text(context.l10n.logs),
|
title: Text(context.l10n.download_location),
|
||||||
|
subtitle: Text(preferences.downloadLocation),
|
||||||
|
trailing: FilledButton(
|
||||||
|
onPressed: pickDownloadLocation,
|
||||||
|
child: const Icon(SpotubeIcons.folder),
|
||||||
|
),
|
||||||
|
onTap: pickDownloadLocation,
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
secondary: const Icon(SpotubeIcons.lyrics),
|
||||||
|
title: Text(context.l10n.download_lyrics),
|
||||||
|
value: preferences.saveTrackLyrics,
|
||||||
|
onChanged: (state) {
|
||||||
|
preferences.setSaveTrackLyrics(state);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (DesktopTools.platform.isDesktop)
|
||||||
|
SectionCardWithHeading(
|
||||||
|
heading: context.l10n.desktop,
|
||||||
|
children: [
|
||||||
|
AdaptiveSelectTile<CloseBehavior>(
|
||||||
|
secondary: const Icon(SpotubeIcons.close),
|
||||||
|
title: Text(context.l10n.close_behavior),
|
||||||
|
value: preferences.closeBehavior,
|
||||||
|
options: [
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: CloseBehavior.close,
|
||||||
|
child: Text(context.l10n.close),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: CloseBehavior.minimizeToTray,
|
||||||
|
child: Text(context.l10n.minimize_to_tray),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (value) {
|
||||||
|
if (value != null) {
|
||||||
|
preferences.setCloseBehavior(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
secondary: const Icon(SpotubeIcons.tray),
|
||||||
|
title: Text(context.l10n.show_tray_icon),
|
||||||
|
value: preferences.showSystemTrayIcon,
|
||||||
|
onChanged: preferences.setShowSystemTrayIcon,
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
secondary: const Icon(SpotubeIcons.window),
|
||||||
|
title: Text(context.l10n.use_system_title_bar),
|
||||||
|
value: preferences.systemTitleBar,
|
||||||
|
onChanged: preferences.setSystemTitleBar,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (!kIsWeb)
|
||||||
|
SectionCardWithHeading(
|
||||||
|
heading: context.l10n.developers,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(SpotubeIcons.logs),
|
||||||
|
title: Text(context.l10n.logs),
|
||||||
|
trailing: const Icon(SpotubeIcons.angleRight),
|
||||||
|
onTap: () {
|
||||||
|
GoRouter.of(context).push("/settings/logs");
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SectionCardWithHeading(
|
||||||
|
heading: context.l10n.about,
|
||||||
|
children: [
|
||||||
|
AdaptiveListTile(
|
||||||
|
leading: const Icon(
|
||||||
|
SpotubeIcons.heart,
|
||||||
|
color: Colors.pink,
|
||||||
|
),
|
||||||
|
title: SizedBox(
|
||||||
|
height: 50,
|
||||||
|
width: 200,
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: AutoSizeText(
|
||||||
|
context.l10n.u_love_spotube,
|
||||||
|
maxLines: 1,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.pink,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: (context, update) => FilledButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor:
|
||||||
|
MaterialStatePropertyAll(Colors.red[100]),
|
||||||
|
foregroundColor: const MaterialStatePropertyAll(
|
||||||
|
Colors.pinkAccent),
|
||||||
|
padding: const MaterialStatePropertyAll(
|
||||||
|
EdgeInsets.all(15)),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
launchUrlString(
|
||||||
|
"https://opencollective.com/spotube",
|
||||||
|
mode: LaunchMode.externalApplication,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(SpotubeIcons.heart),
|
||||||
|
const SizedBox(width: 5),
|
||||||
|
Text(context.l10n.please_sponsor),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (Env.enableUpdateChecker)
|
||||||
|
SwitchListTile(
|
||||||
|
secondary: const Icon(SpotubeIcons.update),
|
||||||
|
title: Text(context.l10n.check_for_updates),
|
||||||
|
value: preferences.checkUpdate,
|
||||||
|
onChanged: (checked) =>
|
||||||
|
preferences.setCheckUpdate(checked),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(SpotubeIcons.info),
|
||||||
|
title: Text(context.l10n.about_spotube),
|
||||||
trailing: const Icon(SpotubeIcons.angleRight),
|
trailing: const Icon(SpotubeIcons.angleRight),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
GoRouter.of(context).push("/settings/logs");
|
GoRouter.of(context).push("/settings/about");
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SectionCardWithHeading(
|
Center(
|
||||||
heading: context.l10n.about,
|
child: FilledButton(
|
||||||
children: [
|
onPressed: preferences.reset,
|
||||||
AdaptiveListTile(
|
child: Text(context.l10n.restore_defaults),
|
||||||
leading: const Icon(
|
|
||||||
SpotubeIcons.heart,
|
|
||||||
color: Colors.pink,
|
|
||||||
),
|
|
||||||
title: SizedBox(
|
|
||||||
height: 50,
|
|
||||||
width: 200,
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: AutoSizeText(
|
|
||||||
context.l10n.u_love_spotube,
|
|
||||||
maxLines: 1,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.pink,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
trailing: (context, update) => FilledButton(
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor:
|
|
||||||
MaterialStatePropertyAll(Colors.red[100]),
|
|
||||||
foregroundColor: const MaterialStatePropertyAll(
|
|
||||||
Colors.pinkAccent),
|
|
||||||
padding: const MaterialStatePropertyAll(
|
|
||||||
EdgeInsets.all(15)),
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
launchUrlString(
|
|
||||||
"https://opencollective.com/spotube",
|
|
||||||
mode: LaunchMode.externalApplication,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Icon(SpotubeIcons.heart),
|
|
||||||
const SizedBox(width: 5),
|
|
||||||
Text(context.l10n.please_sponsor),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (Env.enableUpdateChecker)
|
|
||||||
SwitchListTile(
|
|
||||||
secondary: const Icon(SpotubeIcons.update),
|
|
||||||
title: Text(context.l10n.check_for_updates),
|
|
||||||
value: preferences.checkUpdate,
|
|
||||||
onChanged: (checked) =>
|
|
||||||
preferences.setCheckUpdate(checked),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(SpotubeIcons.info),
|
|
||||||
title: Text(context.l10n.about_spotube),
|
|
||||||
trailing: const Icon(SpotubeIcons.angleRight),
|
|
||||||
onTap: () {
|
|
||||||
GoRouter.of(context).push("/settings/about");
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Center(
|
|
||||||
child: FilledButton(
|
|
||||||
onPressed: preferences.reset,
|
|
||||||
child: Text(context.l10n.restore_defaults),
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 10),
|
||||||
const SizedBox(height: 10),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -69,14 +69,8 @@ ThemeData theme(Color seed, Brightness brightness, bool isAmoled) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
scrollbarTheme: DesktopTools.platform.isMobile
|
scrollbarTheme: const ScrollbarThemeData(
|
||||||
? const ScrollbarThemeData(
|
thickness: MaterialStatePropertyAll(14),
|
||||||
interactive: true,
|
),
|
||||||
thickness: MaterialStatePropertyAll(18),
|
|
||||||
minThumbLength: 20,
|
|
||||||
)
|
|
||||||
: const ScrollbarThemeData(
|
|
||||||
thickness: MaterialStatePropertyAll(14),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user