mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-05-08 16:24:36 +00:00
feat: add play in remote device support
This commit is contained in:
parent
940b79c880
commit
2f3a2e671d
@ -2,11 +2,14 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/components/shared/dialogs/select_device_dialog.dart';
|
||||||
import 'package:spotube/components/shared/playbutton_card.dart';
|
import 'package:spotube/components/shared/playbutton_card.dart';
|
||||||
import 'package:spotube/extensions/artist_simple.dart';
|
import 'package:spotube/extensions/artist_simple.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/extensions/image.dart';
|
import 'package:spotube/extensions/image.dart';
|
||||||
import 'package:spotube/extensions/track.dart';
|
import 'package:spotube/extensions/track.dart';
|
||||||
|
import 'package:spotube/models/connect/connect.dart';
|
||||||
|
import 'package:spotube/provider/connect/connect.dart';
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
@ -72,8 +75,19 @@ class AlbumCard extends HookConsumerWidget {
|
|||||||
|
|
||||||
if (fetchedTracks.isEmpty) return;
|
if (fetchedTracks.isEmpty) return;
|
||||||
|
|
||||||
await playlistNotifier.load(fetchedTracks, autoPlay: true);
|
final isRemoteDevice = await showSelectDeviceDialog(context, ref);
|
||||||
playlistNotifier.addCollection(album.id!);
|
if (isRemoteDevice) {
|
||||||
|
final remotePlayback = ref.read(connectProvider.notifier);
|
||||||
|
await remotePlayback.load(
|
||||||
|
WebSocketLoadEventData(
|
||||||
|
tracks: fetchedTracks,
|
||||||
|
collectionId: album.id!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await playlistNotifier.load(fetchedTracks, autoPlay: true);
|
||||||
|
playlistNotifier.addCollection(album.id!);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
updating.value = false;
|
updating.value = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,11 +12,14 @@ class ConnectDeviceButton extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final ThemeData(:colorScheme) = Theme.of(context);
|
final ThemeData(:colorScheme) = Theme.of(context);
|
||||||
|
final pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||||
final connectClients = ref.watch(connectClientsProvider);
|
final connectClients = ref.watch(connectClientsProvider);
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 40,
|
height: 40 * pixelRatio,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
fit: StackFit.loose,
|
||||||
children: [
|
children: [
|
||||||
Center(
|
Center(
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
@ -66,8 +69,10 @@ class ConnectDeviceButton extends HookConsumerWidget {
|
|||||||
right: 0,
|
right: 0,
|
||||||
child: IconButton.filled(
|
child: IconButton.filled(
|
||||||
icon: const Icon(SpotubeIcons.speaker),
|
icon: const Icon(SpotubeIcons.speaker),
|
||||||
style:
|
style: IconButton.styleFrom(
|
||||||
IconButton.styleFrom(foregroundColor: colorScheme.onPrimary),
|
visualDensity: VisualDensity.standard,
|
||||||
|
foregroundColor: colorScheme.onPrimary,
|
||||||
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ServiceUtils.push(context, "/connect");
|
ServiceUtils.push(context, "/connect");
|
||||||
},
|
},
|
||||||
|
|||||||
@ -231,7 +231,7 @@ class PlayerQueue extends HookConsumerWidget {
|
|||||||
onReorder(oldIndex, newIndex);
|
onReorder(oldIndex, newIndex);
|
||||||
},
|
},
|
||||||
scrollController: controller,
|
scrollController: controller,
|
||||||
itemCount: tracks.length,
|
itemCount: tracks.length + 1,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
buildDefaultDragHandles: false,
|
buildDefaultDragHandles: false,
|
||||||
onReorderStart: (index) {
|
onReorderStart: (index) {
|
||||||
@ -241,6 +241,15 @@ class PlayerQueue extends HookConsumerWidget {
|
|||||||
HapticFeedback.selectionClick();
|
HapticFeedback.selectionClick();
|
||||||
},
|
},
|
||||||
itemBuilder: (context, i) {
|
itemBuilder: (context, i) {
|
||||||
|
if (i == tracks.length) {
|
||||||
|
return AutoScrollTag(
|
||||||
|
index: i,
|
||||||
|
controller: controller,
|
||||||
|
key: const ValueKey('end'),
|
||||||
|
child: const Gap(100),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final track = tracks.elementAt(i);
|
final track = tracks.elementAt(i);
|
||||||
return AutoScrollTag(
|
return AutoScrollTag(
|
||||||
key: ValueKey(i),
|
key: ValueKey(i),
|
||||||
@ -277,8 +286,12 @@ class PlayerQueue extends HookConsumerWidget {
|
|||||||
controller: controller,
|
controller: controller,
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
itemCount: filteredTracks.length,
|
itemCount: filteredTracks.length + 1,
|
||||||
itemBuilder: (context, i) {
|
itemBuilder: (context, i) {
|
||||||
|
if (i == filteredTracks.length) {
|
||||||
|
return const Gap(100);
|
||||||
|
}
|
||||||
|
|
||||||
final track = filteredTracks.elementAt(i);
|
final track = filteredTracks.elementAt(i);
|
||||||
return Padding(
|
return Padding(
|
||||||
padding:
|
padding:
|
||||||
@ -299,7 +312,6 @@ class PlayerQueue extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(100),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -2,8 +2,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
|
import 'package:spotube/components/shared/dialogs/select_device_dialog.dart';
|
||||||
import 'package:spotube/components/shared/playbutton_card.dart';
|
import 'package:spotube/components/shared/playbutton_card.dart';
|
||||||
import 'package:spotube/extensions/image.dart';
|
import 'package:spotube/extensions/image.dart';
|
||||||
|
import 'package:spotube/models/connect/connect.dart';
|
||||||
|
import 'package:spotube/provider/connect/connect.dart';
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
@ -71,8 +74,19 @@ class PlaylistCard extends HookConsumerWidget {
|
|||||||
|
|
||||||
if (fetchedTracks.isEmpty) return;
|
if (fetchedTracks.isEmpty) return;
|
||||||
|
|
||||||
await playlistNotifier.load(fetchedTracks, autoPlay: true);
|
final isRemoteDevice = await showSelectDeviceDialog(context, ref);
|
||||||
playlistNotifier.addCollection(playlist.id!);
|
if (isRemoteDevice) {
|
||||||
|
final remotePlayback = ref.read(connectProvider.notifier);
|
||||||
|
await remotePlayback.load(
|
||||||
|
WebSocketLoadEventData(
|
||||||
|
tracks: fetchedTracks,
|
||||||
|
collectionId: playlist.id!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await playlistNotifier.load(fetchedTracks, autoPlay: true);
|
||||||
|
playlistNotifier.addCollection(playlist.id!);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
updating.value = false;
|
updating.value = false;
|
||||||
|
|||||||
70
lib/components/shared/dialogs/select_device_dialog.dart
Normal file
70
lib/components/shared/dialogs/select_device_dialog.dart
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:spotube/extensions/context.dart';
|
||||||
|
import 'package:spotube/provider/connect/clients.dart';
|
||||||
|
|
||||||
|
class SelectDeviceDialog extends HookConsumerWidget {
|
||||||
|
const SelectDeviceDialog({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, ref) {
|
||||||
|
final isRemoteService = useState(false);
|
||||||
|
|
||||||
|
final connectClients = ref.watch(connectClientsProvider);
|
||||||
|
final remoteService = connectClients.asData!.value.resolvedService!;
|
||||||
|
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text("Choose the device:"),
|
||||||
|
insetPadding: const EdgeInsets.all(16),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
"There are multiple device connected.\n"
|
||||||
|
"Choose the device you want this action to take place",
|
||||||
|
),
|
||||||
|
RadioListTile.adaptive(
|
||||||
|
title: Text(remoteService.name),
|
||||||
|
value: true,
|
||||||
|
groupValue: isRemoteService.value,
|
||||||
|
onChanged: (value) {
|
||||||
|
isRemoteService.value = value!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
RadioListTile.adaptive(
|
||||||
|
title: const Text("This Device"),
|
||||||
|
value: false,
|
||||||
|
groupValue: isRemoteService.value,
|
||||||
|
onChanged: (value) {
|
||||||
|
isRemoteService.value = !value!;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(isRemoteService.value);
|
||||||
|
},
|
||||||
|
child: Text(context.l10n.select),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> showSelectDeviceDialog(BuildContext context, WidgetRef ref) async {
|
||||||
|
final connectClients = ref.read(connectClientsProvider);
|
||||||
|
|
||||||
|
if (connectClients.asData?.value.resolvedService == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final isRemote = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const SelectDeviceDialog(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return isRemote ?? false;
|
||||||
|
}
|
||||||
@ -8,12 +8,15 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:skeletonizer/skeletonizer.dart';
|
import 'package:skeletonizer/skeletonizer.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/fake.dart';
|
import 'package:spotube/collections/fake.dart';
|
||||||
|
import 'package:spotube/components/shared/dialogs/select_device_dialog.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/track_tile/track_tile.dart';
|
import 'package:spotube/components/shared/track_tile/track_tile.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/sections/body/track_view_body_headers.dart';
|
import 'package:spotube/components/shared/tracks_view/sections/body/track_view_body_headers.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/sections/body/use_is_user_playlist.dart';
|
import 'package:spotube/components/shared/tracks_view/sections/body/use_is_user_playlist.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
|
import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/track_view_provider.dart';
|
import 'package:spotube/components/shared/tracks_view/track_view_provider.dart';
|
||||||
|
import 'package:spotube/models/connect/connect.dart';
|
||||||
|
import 'package:spotube/provider/connect/connect.dart';
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
import 'package:spotube/utils/service_utils.dart';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||||
@ -131,16 +134,37 @@ class TrackViewBodySection extends HookConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isActive || playlist.tracks.contains(track)) {
|
final isRemoteDevice =
|
||||||
await playlistNotifier.jumpToTrack(track);
|
await showSelectDeviceDialog(context, ref);
|
||||||
|
|
||||||
|
if (isRemoteDevice) {
|
||||||
|
final remotePlayback = ref.read(connectProvider.notifier);
|
||||||
|
final remoteQueue = ref.read(queueProvider);
|
||||||
|
if (remoteQueue.collections.contains(props.collectionId) ||
|
||||||
|
remoteQueue.tracks.any((s) => s.id == track.id)) {
|
||||||
|
await playlistNotifier.jumpToTrack(track);
|
||||||
|
} else {
|
||||||
|
final tracks = await props.pagination.onFetchAll();
|
||||||
|
await remotePlayback.load(
|
||||||
|
WebSocketLoadEventData(
|
||||||
|
tracks: tracks,
|
||||||
|
collectionId: props.collectionId,
|
||||||
|
initialIndex: index,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
final tracks = await props.pagination.onFetchAll();
|
if (isActive || playlist.tracks.contains(track)) {
|
||||||
await playlistNotifier.load(
|
await playlistNotifier.jumpToTrack(track);
|
||||||
tracks,
|
} else {
|
||||||
initialIndex: index,
|
final tracks = await props.pagination.onFetchAll();
|
||||||
autoPlay: true,
|
await playlistNotifier.load(
|
||||||
);
|
tracks,
|
||||||
playlistNotifier.addCollection(props.collectionId);
|
initialIndex: index,
|
||||||
|
autoPlay: true,
|
||||||
|
);
|
||||||
|
playlistNotifier.addCollection(props.collectionId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -6,8 +6,11 @@ import 'package:gap/gap.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:palette_generator/palette_generator.dart';
|
import 'package:palette_generator/palette_generator.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
|
import 'package:spotube/components/shared/dialogs/select_device_dialog.dart';
|
||||||
import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
|
import 'package:spotube/components/shared/tracks_view/track_view_props.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
|
import 'package:spotube/models/connect/connect.dart';
|
||||||
|
import 'package:spotube/provider/connect/connect.dart';
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||||
|
|
||||||
@ -43,13 +46,25 @@ class TrackViewHeaderButtons extends HookConsumerWidget {
|
|||||||
|
|
||||||
final allTracks = await props.pagination.onFetchAll();
|
final allTracks = await props.pagination.onFetchAll();
|
||||||
|
|
||||||
await playlistNotifier.load(
|
final isRemoteDevice = await showSelectDeviceDialog(context, ref);
|
||||||
allTracks,
|
if (isRemoteDevice) {
|
||||||
autoPlay: true,
|
final remotePlayback = ref.read(connectProvider.notifier);
|
||||||
initialIndex: Random().nextInt(allTracks.length),
|
await remotePlayback.load(
|
||||||
);
|
WebSocketLoadEventData(
|
||||||
await audioPlayer.setShuffle(true);
|
tracks: allTracks,
|
||||||
playlistNotifier.addCollection(props.collectionId);
|
collectionId: props.collectionId,
|
||||||
|
initialIndex: Random().nextInt(allTracks.length)),
|
||||||
|
);
|
||||||
|
await remotePlayback.setShuffle(true);
|
||||||
|
} else {
|
||||||
|
await playlistNotifier.load(
|
||||||
|
allTracks,
|
||||||
|
autoPlay: true,
|
||||||
|
initialIndex: Random().nextInt(allTracks.length),
|
||||||
|
);
|
||||||
|
await audioPlayer.setShuffle(true);
|
||||||
|
playlistNotifier.addCollection(props.collectionId);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
@ -61,8 +76,19 @@ class TrackViewHeaderButtons extends HookConsumerWidget {
|
|||||||
|
|
||||||
final allTracks = await props.pagination.onFetchAll();
|
final allTracks = await props.pagination.onFetchAll();
|
||||||
|
|
||||||
await playlistNotifier.load(allTracks, autoPlay: true);
|
final isRemoteDevice = await showSelectDeviceDialog(context, ref);
|
||||||
playlistNotifier.addCollection(props.collectionId);
|
if (isRemoteDevice) {
|
||||||
|
final remotePlayback = ref.read(connectProvider.notifier);
|
||||||
|
await remotePlayback.load(
|
||||||
|
WebSocketLoadEventData(
|
||||||
|
tracks: allTracks,
|
||||||
|
collectionId: props.collectionId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await playlistNotifier.load(allTracks, autoPlay: true);
|
||||||
|
playlistNotifier.addCollection(props.collectionId);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -316,5 +316,6 @@
|
|||||||
"browse_anonymously": "Browse Anonymously",
|
"browse_anonymously": "Browse Anonymously",
|
||||||
"enable_connect": "Enable Connect",
|
"enable_connect": "Enable Connect",
|
||||||
"enable_connect_description": "Control Spotube from other devices",
|
"enable_connect_description": "Control Spotube from other devices",
|
||||||
"devices": "Devices"
|
"devices": "Devices",
|
||||||
|
"select": "Select"
|
||||||
}
|
}
|
||||||
@ -95,7 +95,11 @@ class WebSocketEvent<T> {
|
|||||||
EventCallback<WebSocketLoadEvent> callback,
|
EventCallback<WebSocketLoadEvent> callback,
|
||||||
) async {
|
) async {
|
||||||
if (type == WsEvent.load) {
|
if (type == WsEvent.load) {
|
||||||
await callback(WebSocketLoadEvent.fromJson(data as Map<String, dynamic>));
|
await callback(
|
||||||
|
WebSocketLoadEvent(
|
||||||
|
WebSocketLoadEventData.fromJson(data as Map<String, dynamic>),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,8 +4,11 @@ import 'package:skeletonizer/skeletonizer.dart';
|
|||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/collections/fake.dart';
|
import 'package:spotube/collections/fake.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
|
import 'package:spotube/components/shared/dialogs/select_device_dialog.dart';
|
||||||
import 'package:spotube/components/shared/track_tile/track_tile.dart';
|
import 'package:spotube/components/shared/track_tile/track_tile.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
|
import 'package:spotube/models/connect/connect.dart';
|
||||||
|
import 'package:spotube/provider/connect/connect.dart';
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
|
|
||||||
@ -39,16 +42,41 @@ class ArtistPageTopTracks extends HookConsumerWidget {
|
|||||||
|
|
||||||
void playPlaylist(List<Track> tracks, {Track? currentTrack}) async {
|
void playPlaylist(List<Track> tracks, {Track? currentTrack}) async {
|
||||||
currentTrack ??= tracks.first;
|
currentTrack ??= tracks.first;
|
||||||
if (!isPlaylistPlaying) {
|
|
||||||
playlistNotifier.load(
|
final isRemoteDevice = await showSelectDeviceDialog(context, ref);
|
||||||
tracks,
|
if (isRemoteDevice) {
|
||||||
initialIndex: tracks.indexWhere((s) => s.id == currentTrack?.id),
|
final remotePlayback = ref.read(connectProvider.notifier);
|
||||||
autoPlay: true,
|
final remotePlaylist = ref.read(queueProvider);
|
||||||
);
|
|
||||||
} else if (isPlaylistPlaying &&
|
final isPlaylistPlaying = remotePlaylist.containsTracks(tracks);
|
||||||
currentTrack.id != null &&
|
|
||||||
currentTrack.id != playlist.activeTrack?.id) {
|
if (!isPlaylistPlaying) {
|
||||||
await playlistNotifier.jumpToTrack(currentTrack);
|
await remotePlayback.load(
|
||||||
|
WebSocketLoadEventData(
|
||||||
|
tracks: tracks,
|
||||||
|
initialIndex: tracks.indexWhere((s) => s.id == currentTrack?.id),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (isPlaylistPlaying &&
|
||||||
|
currentTrack.id != null &&
|
||||||
|
currentTrack.id != remotePlaylist.activeTrack?.id) {
|
||||||
|
final index = playlist.tracks
|
||||||
|
.toList()
|
||||||
|
.indexWhere((s) => s.id == currentTrack!.id);
|
||||||
|
await remotePlayback.jumpTo(index);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!isPlaylistPlaying) {
|
||||||
|
playlistNotifier.load(
|
||||||
|
tracks,
|
||||||
|
initialIndex: tracks.indexWhere((s) => s.id == currentTrack?.id),
|
||||||
|
autoPlay: true,
|
||||||
|
);
|
||||||
|
} else if (isPlaylistPlaying &&
|
||||||
|
currentTrack.id != null &&
|
||||||
|
currentTrack.id != playlist.activeTrack?.id) {
|
||||||
|
await playlistNotifier.jumpToTrack(currentTrack);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,8 +3,11 @@ import 'package:flutter/material.dart' hide Page;
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/components/shared/dialogs/prompt_dialog.dart';
|
import 'package:spotube/components/shared/dialogs/prompt_dialog.dart';
|
||||||
|
import 'package:spotube/components/shared/dialogs/select_device_dialog.dart';
|
||||||
import 'package:spotube/components/shared/track_tile/track_tile.dart';
|
import 'package:spotube/components/shared/track_tile/track_tile.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
|
import 'package:spotube/models/connect/connect.dart';
|
||||||
|
import 'package:spotube/provider/connect/connect.dart';
|
||||||
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
|
||||||
import 'package:spotube/provider/spotify/spotify.dart';
|
import 'package:spotube/provider/spotify/spotify.dart';
|
||||||
|
|
||||||
@ -48,25 +51,58 @@ class SearchTracksSection extends HookConsumerWidget {
|
|||||||
track: track,
|
track: track,
|
||||||
playlist: playlist,
|
playlist: playlist,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final isTrackPlaying = playlist.activeTrack?.id == track.id;
|
final isRemoteDevice =
|
||||||
if (!isTrackPlaying && context.mounted) {
|
await showSelectDeviceDialog(context, ref);
|
||||||
final shouldPlay = (playlist.tracks.length) > 20
|
|
||||||
? await showPromptDialog(
|
|
||||||
context: context,
|
|
||||||
title: context.l10n.playing_track(
|
|
||||||
track.name!,
|
|
||||||
),
|
|
||||||
message: context.l10n.queue_clear_alert(
|
|
||||||
playlist.tracks.length,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: true;
|
|
||||||
|
|
||||||
if (shouldPlay) {
|
if (isRemoteDevice) {
|
||||||
await playlistNotifier.load(
|
final remotePlayback = ref.read(connectProvider.notifier);
|
||||||
[track],
|
final remotePlaylist = ref.read(queueProvider);
|
||||||
autoPlay: true,
|
|
||||||
);
|
final isTrackPlaying =
|
||||||
|
remotePlaylist.activeTrack?.id == track.id;
|
||||||
|
|
||||||
|
if (!isTrackPlaying && context.mounted) {
|
||||||
|
final shouldPlay = (playlist.tracks.length) > 20
|
||||||
|
? await showPromptDialog(
|
||||||
|
context: context,
|
||||||
|
title: context.l10n.playing_track(
|
||||||
|
track.name!,
|
||||||
|
),
|
||||||
|
message: context.l10n.queue_clear_alert(
|
||||||
|
playlist.tracks.length,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: true;
|
||||||
|
|
||||||
|
if (shouldPlay) {
|
||||||
|
await remotePlayback.load(
|
||||||
|
WebSocketLoadEventData(
|
||||||
|
tracks: [track],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final isTrackPlaying = playlist.activeTrack?.id == track.id;
|
||||||
|
if (!isTrackPlaying && context.mounted) {
|
||||||
|
final shouldPlay = (playlist.tracks.length) > 20
|
||||||
|
? await showPromptDialog(
|
||||||
|
context: context,
|
||||||
|
title: context.l10n.playing_track(
|
||||||
|
track.name!,
|
||||||
|
),
|
||||||
|
message: context.l10n.queue_clear_alert(
|
||||||
|
playlist.tracks.length,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: true;
|
||||||
|
|
||||||
|
if (shouldPlay) {
|
||||||
|
await playlistNotifier.load(
|
||||||
|
[track],
|
||||||
|
autoPlay: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import 'package:bonsoir/bonsoir.dart';
|
|||||||
import 'package:spotube/services/device_info/device_info.dart';
|
import 'package:spotube/services/device_info/device_info.dart';
|
||||||
import 'package:spotube/utils/primitive_utils.dart';
|
import 'package:spotube/utils/primitive_utils.dart';
|
||||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||||
|
import 'package:spotube/provider/volume_provider.dart';
|
||||||
|
|
||||||
final logger = getLogger('ConnectServer');
|
final logger = getLogger('ConnectServer');
|
||||||
|
|
||||||
@ -193,7 +194,7 @@ final connectServerProvider = FutureProvider((ref) async {
|
|||||||
});
|
});
|
||||||
|
|
||||||
event.onVolume((event) async {
|
event.onVolume((event) async {
|
||||||
await audioPlayer.setVolume(event.data);
|
ref.read(volumeProvider.notifier).setVolume(event.data);
|
||||||
});
|
});
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
Catcher2.reportCheckedError(e, stackTrace);
|
Catcher2.reportCheckedError(e, stackTrace);
|
||||||
|
|||||||
@ -2,109 +2,127 @@
|
|||||||
"ar": [
|
"ar": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
],
|
],
|
||||||
|
|
||||||
"bn": [
|
"bn": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ca": [
|
"ca": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
],
|
],
|
||||||
|
|
||||||
"de": [
|
"de": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
],
|
],
|
||||||
|
|
||||||
"es": [
|
"es": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fa": [
|
"fa": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fr": [
|
"fr": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
],
|
],
|
||||||
|
|
||||||
"hi": [
|
"hi": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
],
|
],
|
||||||
|
|
||||||
"it": [
|
"it": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ja": [
|
"ja": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ko": [
|
"ko": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ne": [
|
"ne": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
],
|
],
|
||||||
|
|
||||||
"nl": [
|
"nl": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
],
|
],
|
||||||
|
|
||||||
"pl": [
|
"pl": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
],
|
],
|
||||||
|
|
||||||
"pt": [
|
"pt": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ru": [
|
"ru": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
],
|
],
|
||||||
|
|
||||||
"tr": [
|
"tr": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
],
|
],
|
||||||
|
|
||||||
"uk": [
|
"uk": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
],
|
],
|
||||||
|
|
||||||
"vi": [
|
"vi": [
|
||||||
@ -112,12 +130,14 @@
|
|||||||
"no_lyrics_available",
|
"no_lyrics_available",
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
],
|
],
|
||||||
|
|
||||||
"zh": [
|
"zh": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices"
|
"devices",
|
||||||
|
"select"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user