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:hooks_riverpod/hooks_riverpod.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/extensions/artist_simple.dart';
|
||||
import 'package:spotube/extensions/context.dart';
|
||||
import 'package:spotube/extensions/image.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/spotify/spotify.dart';
|
||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||
@ -72,8 +75,19 @@ class AlbumCard extends HookConsumerWidget {
|
||||
|
||||
if (fetchedTracks.isEmpty) return;
|
||||
|
||||
await playlistNotifier.load(fetchedTracks, autoPlay: true);
|
||||
playlistNotifier.addCollection(album.id!);
|
||||
final isRemoteDevice = await showSelectDeviceDialog(context, ref);
|
||||
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 {
|
||||
updating.value = false;
|
||||
}
|
||||
|
||||
@ -12,11 +12,14 @@ class ConnectDeviceButton extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final ThemeData(:colorScheme) = Theme.of(context);
|
||||
final pixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||
final connectClients = ref.watch(connectClientsProvider);
|
||||
|
||||
return SizedBox(
|
||||
height: 40,
|
||||
height: 40 * pixelRatio,
|
||||
child: Stack(
|
||||
alignment: Alignment.centerRight,
|
||||
fit: StackFit.loose,
|
||||
children: [
|
||||
Center(
|
||||
child: InkWell(
|
||||
@ -66,8 +69,10 @@ class ConnectDeviceButton extends HookConsumerWidget {
|
||||
right: 0,
|
||||
child: IconButton.filled(
|
||||
icon: const Icon(SpotubeIcons.speaker),
|
||||
style:
|
||||
IconButton.styleFrom(foregroundColor: colorScheme.onPrimary),
|
||||
style: IconButton.styleFrom(
|
||||
visualDensity: VisualDensity.standard,
|
||||
foregroundColor: colorScheme.onPrimary,
|
||||
),
|
||||
onPressed: () {
|
||||
ServiceUtils.push(context, "/connect");
|
||||
},
|
||||
|
||||
@ -231,7 +231,7 @@ class PlayerQueue extends HookConsumerWidget {
|
||||
onReorder(oldIndex, newIndex);
|
||||
},
|
||||
scrollController: controller,
|
||||
itemCount: tracks.length,
|
||||
itemCount: tracks.length + 1,
|
||||
shrinkWrap: true,
|
||||
buildDefaultDragHandles: false,
|
||||
onReorderStart: (index) {
|
||||
@ -241,6 +241,15 @@ class PlayerQueue extends HookConsumerWidget {
|
||||
HapticFeedback.selectionClick();
|
||||
},
|
||||
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);
|
||||
return AutoScrollTag(
|
||||
key: ValueKey(i),
|
||||
@ -277,8 +286,12 @@ class PlayerQueue extends HookConsumerWidget {
|
||||
controller: controller,
|
||||
child: ListView.builder(
|
||||
controller: controller,
|
||||
itemCount: filteredTracks.length,
|
||||
itemCount: filteredTracks.length + 1,
|
||||
itemBuilder: (context, i) {
|
||||
if (i == filteredTracks.length) {
|
||||
return const Gap(100);
|
||||
}
|
||||
|
||||
final track = filteredTracks.elementAt(i);
|
||||
return 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:hooks_riverpod/hooks_riverpod.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/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/spotify/spotify.dart';
|
||||
import 'package:spotube/services/audio_player/audio_player.dart';
|
||||
@ -71,8 +74,19 @@ class PlaylistCard extends HookConsumerWidget {
|
||||
|
||||
if (fetchedTracks.isEmpty) return;
|
||||
|
||||
await playlistNotifier.load(fetchedTracks, autoPlay: true);
|
||||
playlistNotifier.addCollection(playlist.id!);
|
||||
final isRemoteDevice = await showSelectDeviceDialog(context, ref);
|
||||
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 {
|
||||
if (context.mounted) {
|
||||
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:spotify/spotify.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/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/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_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/utils/service_utils.dart';
|
||||
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
|
||||
@ -131,16 +134,37 @@ class TrackViewBodySection extends HookConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isActive || playlist.tracks.contains(track)) {
|
||||
await playlistNotifier.jumpToTrack(track);
|
||||
final isRemoteDevice =
|
||||
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 {
|
||||
final tracks = await props.pagination.onFetchAll();
|
||||
await playlistNotifier.load(
|
||||
tracks,
|
||||
initialIndex: index,
|
||||
autoPlay: true,
|
||||
);
|
||||
playlistNotifier.addCollection(props.collectionId);
|
||||
if (isActive || playlist.tracks.contains(track)) {
|
||||
await playlistNotifier.jumpToTrack(track);
|
||||
} else {
|
||||
final tracks = await props.pagination.onFetchAll();
|
||||
await playlistNotifier.load(
|
||||
tracks,
|
||||
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:palette_generator/palette_generator.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/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/services/audio_player/audio_player.dart';
|
||||
|
||||
@ -43,13 +46,25 @@ class TrackViewHeaderButtons extends HookConsumerWidget {
|
||||
|
||||
final allTracks = await props.pagination.onFetchAll();
|
||||
|
||||
await playlistNotifier.load(
|
||||
allTracks,
|
||||
autoPlay: true,
|
||||
initialIndex: Random().nextInt(allTracks.length),
|
||||
);
|
||||
await audioPlayer.setShuffle(true);
|
||||
playlistNotifier.addCollection(props.collectionId);
|
||||
final isRemoteDevice = await showSelectDeviceDialog(context, ref);
|
||||
if (isRemoteDevice) {
|
||||
final remotePlayback = ref.read(connectProvider.notifier);
|
||||
await remotePlayback.load(
|
||||
WebSocketLoadEventData(
|
||||
tracks: allTracks,
|
||||
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 {
|
||||
isLoading.value = false;
|
||||
}
|
||||
@ -61,8 +76,19 @@ class TrackViewHeaderButtons extends HookConsumerWidget {
|
||||
|
||||
final allTracks = await props.pagination.onFetchAll();
|
||||
|
||||
await playlistNotifier.load(allTracks, autoPlay: true);
|
||||
playlistNotifier.addCollection(props.collectionId);
|
||||
final isRemoteDevice = await showSelectDeviceDialog(context, ref);
|
||||
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 {
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
@ -316,5 +316,6 @@
|
||||
"browse_anonymously": "Browse Anonymously",
|
||||
"enable_connect": "Enable Connect",
|
||||
"enable_connect_description": "Control Spotube from other devices",
|
||||
"devices": "Devices"
|
||||
"devices": "Devices",
|
||||
"select": "Select"
|
||||
}
|
||||
@ -95,7 +95,11 @@ class WebSocketEvent<T> {
|
||||
EventCallback<WebSocketLoadEvent> callback,
|
||||
) async {
|
||||
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:spotube/collections/fake.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/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/spotify/spotify.dart';
|
||||
|
||||
@ -39,16 +42,41 @@ class ArtistPageTopTracks extends HookConsumerWidget {
|
||||
|
||||
void playPlaylist(List<Track> tracks, {Track? currentTrack}) async {
|
||||
currentTrack ??= tracks.first;
|
||||
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);
|
||||
|
||||
final isRemoteDevice = await showSelectDeviceDialog(context, ref);
|
||||
if (isRemoteDevice) {
|
||||
final remotePlayback = ref.read(connectProvider.notifier);
|
||||
final remotePlaylist = ref.read(queueProvider);
|
||||
|
||||
final isPlaylistPlaying = remotePlaylist.containsTracks(tracks);
|
||||
|
||||
if (!isPlaylistPlaying) {
|
||||
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:spotify/spotify.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/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/spotify/spotify.dart';
|
||||
|
||||
@ -48,25 +51,58 @@ class SearchTracksSection extends HookConsumerWidget {
|
||||
track: track,
|
||||
playlist: playlist,
|
||||
onTap: () async {
|
||||
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;
|
||||
final isRemoteDevice =
|
||||
await showSelectDeviceDialog(context, ref);
|
||||
|
||||
if (shouldPlay) {
|
||||
await playlistNotifier.load(
|
||||
[track],
|
||||
autoPlay: true,
|
||||
);
|
||||
if (isRemoteDevice) {
|
||||
final remotePlayback = ref.read(connectProvider.notifier);
|
||||
final remotePlaylist = ref.read(queueProvider);
|
||||
|
||||
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/utils/primitive_utils.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
import 'package:spotube/provider/volume_provider.dart';
|
||||
|
||||
final logger = getLogger('ConnectServer');
|
||||
|
||||
@ -193,7 +194,7 @@ final connectServerProvider = FutureProvider((ref) async {
|
||||
});
|
||||
|
||||
event.onVolume((event) async {
|
||||
await audioPlayer.setVolume(event.data);
|
||||
ref.read(volumeProvider.notifier).setVolume(event.data);
|
||||
});
|
||||
} catch (e, stackTrace) {
|
||||
Catcher2.reportCheckedError(e, stackTrace);
|
||||
|
||||
@ -2,109 +2,127 @@
|
||||
"ar": [
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
],
|
||||
|
||||
"bn": [
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
],
|
||||
|
||||
"ca": [
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
],
|
||||
|
||||
"de": [
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
],
|
||||
|
||||
"es": [
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
],
|
||||
|
||||
"fa": [
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
],
|
||||
|
||||
"fr": [
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
],
|
||||
|
||||
"hi": [
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
],
|
||||
|
||||
"it": [
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
],
|
||||
|
||||
"ja": [
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
],
|
||||
|
||||
"ko": [
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
],
|
||||
|
||||
"ne": [
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
],
|
||||
|
||||
"nl": [
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
],
|
||||
|
||||
"pl": [
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
],
|
||||
|
||||
"pt": [
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
],
|
||||
|
||||
"ru": [
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
],
|
||||
|
||||
"tr": [
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
],
|
||||
|
||||
"uk": [
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
],
|
||||
|
||||
"vi": [
|
||||
@ -112,12 +130,14 @@
|
||||
"no_lyrics_available",
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
],
|
||||
|
||||
"zh": [
|
||||
"enable_connect",
|
||||
"enable_connect_description",
|
||||
"devices"
|
||||
"devices",
|
||||
"select"
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user