mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
refactor: show queue from side in desktop
This commit is contained in:
parent
da04f068f9
commit
a1cc44759b
@ -5,10 +5,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
|
|
||||||
import 'package:spotify/spotify.dart' hide Offset;
|
import 'package:spotify/spotify.dart' hide Offset;
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/player/player_queue.dart';
|
|
||||||
import 'package:spotube/components/player/sibling_tracks_sheet.dart';
|
import 'package:spotube/components/player/sibling_tracks_sheet.dart';
|
||||||
import 'package:spotube/components/shared/adaptive/adaptive_pop_sheet_list.dart';
|
import 'package:spotube/components/shared/adaptive/adaptive_pop_sheet_list.dart';
|
||||||
import 'package:spotube/components/shared/heart_button.dart';
|
import 'package:spotube/components/shared/heart_button.dart';
|
||||||
|
import 'package:spotube/extensions/constrains.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/extensions/duration.dart';
|
import 'package:spotube/extensions/duration.dart';
|
||||||
import 'package:spotube/models/local_track.dart';
|
import 'package:spotube/models/local_track.dart';
|
||||||
@ -35,6 +35,7 @@ class PlayerActions extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
|
final mediaQuery = MediaQuery.of(context);
|
||||||
final playlist = ref.watch(ProxyPlaylistNotifier.provider);
|
final playlist = ref.watch(ProxyPlaylistNotifier.provider);
|
||||||
final isLocalTrack = playlist.activeTrack is LocalTrack;
|
final isLocalTrack = playlist.activeTrack is LocalTrack;
|
||||||
ref.watch(downloadManagerProvider);
|
ref.watch(downloadManagerProvider);
|
||||||
@ -86,23 +87,7 @@ class PlayerActions extends HookConsumerWidget {
|
|||||||
tooltip: context.l10n.queue,
|
tooltip: context.l10n.queue,
|
||||||
onPressed: playlist.activeTrack != null
|
onPressed: playlist.activeTrack != null
|
||||||
? () {
|
? () {
|
||||||
showModalBottomSheet(
|
Scaffold.of(context).openEndDrawer();
|
||||||
context: context,
|
|
||||||
isDismissible: true,
|
|
||||||
enableDrag: true,
|
|
||||||
isScrollControlled: true,
|
|
||||||
backgroundColor: Colors.black12,
|
|
||||||
barrierColor: Colors.black12,
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
maxHeight: MediaQuery.of(context).size.height * .7,
|
|
||||||
),
|
|
||||||
builder: (context) {
|
|
||||||
return PlayerQueue(floating: floatingQueue);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
@ -119,6 +104,7 @@ class PlayerActions extends HookConsumerWidget {
|
|||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
backgroundColor: Colors.black12,
|
backgroundColor: Colors.black12,
|
||||||
barrierColor: Colors.black12,
|
barrierColor: Colors.black12,
|
||||||
|
elevation: 0,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
|
@ -36,7 +36,9 @@ class PlayerQueue extends HookConsumerWidget {
|
|||||||
|
|
||||||
final tracks = playlist.tracks;
|
final tracks = playlist.tracks;
|
||||||
final borderRadius = floating
|
final borderRadius = floating
|
||||||
? BorderRadius.circular(10)
|
? const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(10),
|
||||||
|
)
|
||||||
: const BorderRadius.only(
|
: const BorderRadius.only(
|
||||||
topLeft: Radius.circular(10),
|
topLeft: Radius.circular(10),
|
||||||
topRight: Radius.circular(10),
|
topRight: Radius.circular(10),
|
||||||
@ -80,140 +82,177 @@ class PlayerQueue extends HookConsumerWidget {
|
|||||||
return const NotFound(vertical: true);
|
return const NotFound(vertical: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return BackdropFilter(
|
return ClipRRect(
|
||||||
filter: ImageFilter.blur(
|
borderRadius: borderRadius,
|
||||||
sigmaX: 12.0,
|
clipBehavior: Clip.hardEdge,
|
||||||
sigmaY: 12.0,
|
child: BackdropFilter(
|
||||||
),
|
filter: ImageFilter.blur(
|
||||||
child: Container(
|
sigmaX: 15,
|
||||||
margin: EdgeInsets.all(floating ? 8.0 : 0),
|
sigmaY: 15,
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: 5.0,
|
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
child: Container(
|
||||||
color: theme.scaffoldBackgroundColor.withOpacity(0.5),
|
padding: const EdgeInsets.only(
|
||||||
borderRadius: borderRadius,
|
top: 5.0,
|
||||||
),
|
),
|
||||||
child: CallbackShortcuts(
|
decoration: BoxDecoration(
|
||||||
bindings: {
|
color: theme.colorScheme.surfaceVariant.withOpacity(0.5),
|
||||||
LogicalKeySet(LogicalKeyboardKey.escape): () {
|
borderRadius: borderRadius,
|
||||||
if (!isSearching.value) {
|
),
|
||||||
Navigator.of(context).pop();
|
child: CallbackShortcuts(
|
||||||
|
bindings: {
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.escape): () {
|
||||||
|
if (!isSearching.value) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
isSearching.value = false;
|
||||||
|
searchText.value = '';
|
||||||
}
|
}
|
||||||
isSearching.value = false;
|
},
|
||||||
searchText.value = '';
|
child: LayoutBuilder(builder: (context, constraints) {
|
||||||
}
|
return Column(
|
||||||
},
|
children: [
|
||||||
child: LayoutBuilder(builder: (context, constraints) {
|
if (!floating)
|
||||||
return Column(
|
Container(
|
||||||
children: [
|
height: 5,
|
||||||
Container(
|
width: 100,
|
||||||
height: 5,
|
margin: const EdgeInsets.only(bottom: 5, top: 2),
|
||||||
width: 100,
|
decoration: BoxDecoration(
|
||||||
margin: const EdgeInsets.only(bottom: 5, top: 2),
|
color: headlineColor,
|
||||||
decoration: BoxDecoration(
|
borderRadius: BorderRadius.circular(20),
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const Spacer(),
|
),
|
||||||
],
|
Row(
|
||||||
if (constraints.mdAndUp || isSearching.value)
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
TextField(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
onChanged: (value) {
|
children: [
|
||||||
searchText.value = value;
|
if (constraints.mdAndUp || !isSearching.value) ...[
|
||||||
},
|
const SizedBox(width: 10),
|
||||||
decoration: InputDecoration(
|
Text(
|
||||||
hintText: context.l10n.search,
|
context.l10n.tracks_in_queue(tracks.length),
|
||||||
isDense: true,
|
style: TextStyle(
|
||||||
prefixIcon: constraints.smAndDown
|
color: headlineColor,
|
||||||
? IconButton(
|
fontWeight: FontWeight.bold,
|
||||||
icon: const Icon(
|
fontSize: 18,
|
||||||
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,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
const Spacer(),
|
||||||
else
|
],
|
||||||
IconButton.filledTonal(
|
if (constraints.mdAndUp || isSearching.value)
|
||||||
icon: const Icon(SpotubeIcons.filter),
|
TextField(
|
||||||
onPressed: () {
|
onChanged: (value) {
|
||||||
isSearching.value = !isSearching.value;
|
searchText.value = value;
|
||||||
},
|
},
|
||||||
),
|
decoration: InputDecoration(
|
||||||
if (constraints.mdAndUp || !isSearching.value) ...[
|
hintText: context.l10n.search,
|
||||||
const SizedBox(width: 10),
|
isDense: true,
|
||||||
FilledButton(
|
prefixIcon: constraints.smAndDown
|
||||||
style: FilledButton.styleFrom(
|
? IconButton(
|
||||||
backgroundColor:
|
icon: const Icon(
|
||||||
theme.scaffoldBackgroundColor.withOpacity(0.5),
|
Icons.arrow_back_ios_new_outlined,
|
||||||
foregroundColor: theme.textTheme.headlineSmall?.color,
|
),
|
||||||
|
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;
|
||||||
|
},
|
||||||
),
|
),
|
||||||
child: Row(
|
if (constraints.mdAndUp || !isSearching.value) ...[
|
||||||
children: [
|
const SizedBox(width: 10),
|
||||||
const Icon(SpotubeIcons.playlistRemove),
|
FilledButton(
|
||||||
const SizedBox(width: 5),
|
style: FilledButton.styleFrom(
|
||||||
Text(context.l10n.clear_all),
|
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();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
onPressed: () {
|
const SizedBox(width: 10),
|
||||||
playlistNotifier.stop();
|
],
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
],
|
],
|
||||||
],
|
),
|
||||||
),
|
const SizedBox(height: 10),
|
||||||
const SizedBox(height: 10),
|
if (!isSearching.value && searchText.value.isEmpty)
|
||||||
if (!isSearching.value && searchText.value.isEmpty)
|
Flexible(
|
||||||
Flexible(
|
child: InterScrollbar(
|
||||||
child: InterScrollbar(
|
controller: controller,
|
||||||
controller: controller,
|
child: ReorderableListView.builder(
|
||||||
child: ReorderableListView.builder(
|
onReorder: (oldIndex, newIndex) {
|
||||||
onReorder: (oldIndex, newIndex) {
|
playlistNotifier.moveTrack(oldIndex, newIndex);
|
||||||
playlistNotifier.moveTrack(oldIndex, newIndex);
|
},
|
||||||
},
|
scrollController: controller,
|
||||||
scrollController: controller,
|
itemCount: tracks.length,
|
||||||
itemCount: tracks.length,
|
shrinkWrap: true,
|
||||||
shrinkWrap: true,
|
buildDefaultDragHandles: false,
|
||||||
buildDefaultDragHandles: false,
|
itemBuilder: (context, i) {
|
||||||
itemBuilder: (context, i) {
|
final track = tracks.elementAt(i);
|
||||||
final track = tracks.elementAt(i);
|
return AutoScrollTag(
|
||||||
return AutoScrollTag(
|
key: ValueKey(i),
|
||||||
key: ValueKey(i),
|
controller: controller,
|
||||||
controller: controller,
|
index: i,
|
||||||
index: i,
|
child: Padding(
|
||||||
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(
|
||||||
@ -225,47 +264,16 @@ class PlayerQueue extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
await playlistNotifier.jumpToTrack(track);
|
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);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -86,151 +86,153 @@ class SiblingTracksSheet extends HookConsumerWidget {
|
|||||||
return null;
|
return null;
|
||||||
}, [playlist.activeTrack]);
|
}, [playlist.activeTrack]);
|
||||||
|
|
||||||
final itemBuilder = useCallback((YoutubeVideoInfo video) {
|
final itemBuilder = useCallback(
|
||||||
return ListTile(
|
(YoutubeVideoInfo video) {
|
||||||
title: Text(video.title),
|
return ListTile(
|
||||||
leading: Padding(
|
title: Text(video.title),
|
||||||
padding: const EdgeInsets.all(8.0),
|
leading: Padding(
|
||||||
child: UniversalImage(
|
padding: const EdgeInsets.all(8.0),
|
||||||
path: video.thumbnailUrl,
|
child: UniversalImage(
|
||||||
height: 60,
|
path: video.thumbnailUrl,
|
||||||
width: 60,
|
height: 60,
|
||||||
|
width: 60,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
shape: RoundedRectangleBorder(
|
||||||
shape: RoundedRectangleBorder(
|
borderRadius: BorderRadius.circular(5),
|
||||||
borderRadius: BorderRadius.circular(5),
|
),
|
||||||
),
|
trailing: Text(video.duration.toHumanReadableString()),
|
||||||
trailing: Text(video.duration.toHumanReadableString()),
|
subtitle: Text(video.channelName),
|
||||||
subtitle: Text(video.channelName),
|
enabled: playlist.isFetching != true,
|
||||||
enabled: playlist.isFetching != true,
|
selected: playlist.isFetching != true &&
|
||||||
selected: playlist.isFetching != true &&
|
video.id == (playlist.activeTrack as SpotubeTrack).ytTrack.id,
|
||||||
video.id == (playlist.activeTrack as SpotubeTrack).ytTrack.id,
|
selectedTileColor: theme.popupMenuTheme.color,
|
||||||
selectedTileColor: theme.popupMenuTheme.color,
|
onTap: () {
|
||||||
onTap: () {
|
if (playlist.isFetching == false &&
|
||||||
if (playlist.isFetching == false &&
|
video.id != (playlist.activeTrack as SpotubeTrack).ytTrack.id) {
|
||||||
video.id != (playlist.activeTrack as SpotubeTrack).ytTrack.id) {
|
playlistNotifier.swapSibling(video);
|
||||||
playlistNotifier.swapSibling(video);
|
Navigator.of(context).pop();
|
||||||
Navigator.of(context).pop();
|
}
|
||||||
}
|
},
|
||||||
},
|
);
|
||||||
);
|
},
|
||||||
}, [
|
[playlist.isFetching, playlist.activeTrack, siblings],
|
||||||
playlist.isFetching,
|
);
|
||||||
playlist.activeTrack,
|
|
||||||
siblings,
|
|
||||||
]);
|
|
||||||
|
|
||||||
var mediaQuery = MediaQuery.of(context);
|
var mediaQuery = MediaQuery.of(context);
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: BackdropFilter(
|
child: ClipRRect(
|
||||||
filter: ImageFilter.blur(
|
borderRadius: borderRadius,
|
||||||
sigmaX: 12.0,
|
clipBehavior: Clip.hardEdge,
|
||||||
sigmaY: 12.0,
|
child: BackdropFilter(
|
||||||
),
|
filter: ImageFilter.blur(
|
||||||
child: AnimatedSize(
|
sigmaX: 12.0,
|
||||||
duration: const Duration(milliseconds: 300),
|
sigmaY: 12.0,
|
||||||
child: Container(
|
),
|
||||||
height: isSearching.value && mediaQuery.smAndDown
|
child: AnimatedSize(
|
||||||
? mediaQuery.size.height
|
duration: const Duration(milliseconds: 300),
|
||||||
: mediaQuery.size.height * .6,
|
child: Container(
|
||||||
margin: const EdgeInsets.all(8.0),
|
height: isSearching.value && mediaQuery.smAndDown
|
||||||
decoration: BoxDecoration(
|
? mediaQuery.size.height - mediaQuery.padding.top
|
||||||
borderRadius: borderRadius,
|
: mediaQuery.size.height * .6,
|
||||||
color: theme.scaffoldBackgroundColor.withOpacity(.3),
|
decoration: BoxDecoration(
|
||||||
),
|
borderRadius: borderRadius,
|
||||||
child: Scaffold(
|
color: theme.colorScheme.surfaceVariant.withOpacity(.5),
|
||||||
backgroundColor: Colors.transparent,
|
),
|
||||||
appBar: AppBar(
|
child: Scaffold(
|
||||||
centerTitle: true,
|
|
||||||
title: AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
child: !isSearching.value
|
|
||||||
? Text(
|
|
||||||
context.l10n.alternative_track_sources,
|
|
||||||
style: theme.textTheme.headlineSmall,
|
|
||||||
)
|
|
||||||
: TextField(
|
|
||||||
autofocus: true,
|
|
||||||
controller: searchController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: context.l10n.search,
|
|
||||||
hintStyle: theme.textTheme.headlineSmall,
|
|
||||||
border: InputBorder.none,
|
|
||||||
),
|
|
||||||
style: theme.textTheme.headlineSmall,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
automaticallyImplyLeading: false,
|
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
actions: [
|
appBar: AppBar(
|
||||||
if (!isSearching.value)
|
centerTitle: true,
|
||||||
IconButton(
|
title: AnimatedSwitcher(
|
||||||
icon: const Icon(SpotubeIcons.search, size: 18),
|
duration: const Duration(milliseconds: 300),
|
||||||
onPressed: () {
|
child: !isSearching.value
|
||||||
isSearching.value = true;
|
? Text(
|
||||||
},
|
context.l10n.alternative_track_sources,
|
||||||
)
|
style: theme.textTheme.headlineSmall,
|
||||||
else ...[
|
)
|
||||||
if (preferences.youtubeApiType == YoutubeApiType.piped)
|
: TextField(
|
||||||
PopupMenuButton(
|
autofocus: true,
|
||||||
icon: const Icon(SpotubeIcons.filter, size: 18),
|
controller: searchController,
|
||||||
onSelected: (SearchMode mode) {
|
decoration: InputDecoration(
|
||||||
searchMode.value = mode;
|
hintText: context.l10n.search,
|
||||||
|
hintStyle: theme.textTheme.headlineSmall,
|
||||||
|
border: InputBorder.none,
|
||||||
|
),
|
||||||
|
style: theme.textTheme.headlineSmall,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
actions: [
|
||||||
|
if (!isSearching.value)
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(SpotubeIcons.search, size: 18),
|
||||||
|
onPressed: () {
|
||||||
|
isSearching.value = true;
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else ...[
|
||||||
|
if (preferences.youtubeApiType == YoutubeApiType.piped)
|
||||||
|
PopupMenuButton(
|
||||||
|
icon: const Icon(SpotubeIcons.filter, size: 18),
|
||||||
|
onSelected: (SearchMode mode) {
|
||||||
|
searchMode.value = mode;
|
||||||
|
},
|
||||||
|
initialValue: searchMode.value,
|
||||||
|
itemBuilder: (context) => SearchMode.values
|
||||||
|
.map(
|
||||||
|
(e) => PopupMenuItem(
|
||||||
|
value: e,
|
||||||
|
child: Text(e.label),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(SpotubeIcons.close, size: 18),
|
||||||
|
onPressed: () {
|
||||||
|
isSearching.value = false;
|
||||||
},
|
},
|
||||||
initialValue: searchMode.value,
|
|
||||||
itemBuilder: (context) => SearchMode.values
|
|
||||||
.map(
|
|
||||||
(e) => PopupMenuItem(
|
|
||||||
value: e,
|
|
||||||
child: Text(e.label),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
),
|
||||||
IconButton(
|
]
|
||||||
icon: const Icon(SpotubeIcons.close, size: 18),
|
],
|
||||||
onPressed: () {
|
),
|
||||||
isSearching.value = false;
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
transitionBuilder: (child, animation) =>
|
||||||
|
FadeTransition(opacity: animation, child: child),
|
||||||
|
child: InterScrollbar(
|
||||||
|
child: switch (isSearching.value) {
|
||||||
|
false => ListView.builder(
|
||||||
|
itemCount: siblings.length,
|
||||||
|
itemBuilder: (context, index) =>
|
||||||
|
itemBuilder(siblings[index]),
|
||||||
|
),
|
||||||
|
true => FutureBuilder(
|
||||||
|
future: searchRequest,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.hasError) {
|
||||||
|
return Center(
|
||||||
|
child: Text(snapshot.error.toString()),
|
||||||
|
);
|
||||||
|
} else if (!snapshot.hasData) {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
return InterScrollbar(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: snapshot.data!.length,
|
||||||
|
itemBuilder: (context, index) =>
|
||||||
|
itemBuilder(snapshot.data![index]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: AnimatedSwitcher(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
transitionBuilder: (child, animation) =>
|
|
||||||
FadeTransition(opacity: animation, child: child),
|
|
||||||
child: InterScrollbar(
|
|
||||||
child: switch (isSearching.value) {
|
|
||||||
false => ListView.builder(
|
|
||||||
itemCount: siblings.length,
|
|
||||||
itemBuilder: (context, index) =>
|
|
||||||
itemBuilder(siblings[index]),
|
|
||||||
),
|
|
||||||
true => FutureBuilder(
|
|
||||||
future: searchRequest,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasError) {
|
|
||||||
return Center(
|
|
||||||
child: Text(snapshot.error.toString()),
|
|
||||||
);
|
|
||||||
} else if (!snapshot.hasData) {
|
|
||||||
return const Center(
|
|
||||||
child: CircularProgressIndicator());
|
|
||||||
}
|
|
||||||
|
|
||||||
return InterScrollbar(
|
|
||||||
child: ListView.builder(
|
|
||||||
itemCount: snapshot.data!.length,
|
|
||||||
itemBuilder: (context, index) =>
|
|
||||||
itemBuilder(snapshot.data![index]),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -3,11 +3,13 @@ import 'dart:async';
|
|||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query/fl_query.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_desktop_tools/flutter_desktop_tools.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
|
import 'package:spotube/components/player/player_queue.dart';
|
||||||
import 'package:spotube/components/shared/dialogs/replace_downloaded_dialog.dart';
|
import 'package:spotube/components/shared/dialogs/replace_downloaded_dialog.dart';
|
||||||
import 'package:spotube/components/root/bottom_player.dart';
|
import 'package:spotube/components/root/bottom_player.dart';
|
||||||
import 'package:spotube/components/root/sidebar.dart';
|
import 'package:spotube/components/root/sidebar.dart';
|
||||||
@ -164,6 +166,22 @@ class RootApp extends HookConsumerWidget {
|
|||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
extendBody: true,
|
extendBody: true,
|
||||||
|
drawerScrimColor: Colors.transparent,
|
||||||
|
endDrawer: DesktopTools.platform.isDesktop
|
||||||
|
? Container(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 800),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
boxShadow: theme.brightness == Brightness.light
|
||||||
|
? null
|
||||||
|
: kElevationToShadow[8],
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.only(
|
||||||
|
top: 40,
|
||||||
|
bottom: 100,
|
||||||
|
),
|
||||||
|
child: const PlayerQueue(floating: true),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
bottomNavigationBar: Column(
|
bottomNavigationBar: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
Loading…
Reference in New Issue
Block a user