mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-05-08 16:24:36 +00:00
feat: show alert when new client connects
This commit is contained in:
parent
2f3a2e671d
commit
0ed358eeb8
@ -317,5 +317,6 @@
|
|||||||
"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"
|
"select": "Select",
|
||||||
|
"connect_client_alert": "You're being controlled by {client}"
|
||||||
}
|
}
|
||||||
@ -16,6 +16,7 @@ import 'package:spotube/components/root/spotube_navigation_bar.dart';
|
|||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
import 'package:spotube/hooks/configurators/use_endless_playback.dart';
|
import 'package:spotube/hooks/configurators/use_endless_playback.dart';
|
||||||
import 'package:spotube/hooks/configurators/use_update_checker.dart';
|
import 'package:spotube/hooks/configurators/use_update_checker.dart';
|
||||||
|
import 'package:spotube/provider/connect/server.dart';
|
||||||
import 'package:spotube/provider/download_manager_provider.dart';
|
import 'package:spotube/provider/download_manager_provider.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/connectivity_adapter.dart';
|
import 'package:spotube/services/connectivity_adapter.dart';
|
||||||
@ -54,50 +55,75 @@ class RootApp extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final subscription = ConnectionCheckerService
|
final subscriptions = [
|
||||||
.instance.onConnectivityChanged
|
ConnectionCheckerService.instance.onConnectivityChanged
|
||||||
.listen((status) {
|
.listen((status) {
|
||||||
if (status) {
|
if (status) {
|
||||||
|
scaffoldMessenger.showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
SpotubeIcons.wifi,
|
||||||
|
color: theme.colorScheme.onPrimary,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(context.l10n.connection_restored),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
backgroundColor: theme.colorScheme.primary,
|
||||||
|
showCloseIcon: true,
|
||||||
|
width: 350,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
scaffoldMessenger.showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
SpotubeIcons.noWifi,
|
||||||
|
color: theme.colorScheme.onError,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(context.l10n.you_are_offline),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
backgroundColor: theme.colorScheme.error,
|
||||||
|
showCloseIcon: true,
|
||||||
|
width: 300,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
connectClientStream.listen((clientOrigin) {
|
||||||
scaffoldMessenger.showSnackBar(
|
scaffoldMessenger.showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
|
backgroundColor: Colors.yellow[600],
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
content: Row(
|
content: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
const Icon(
|
||||||
SpotubeIcons.wifi,
|
SpotubeIcons.error,
|
||||||
color: theme.colorScheme.onPrimary,
|
color: Colors.black,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Text(context.l10n.connection_restored),
|
Text(
|
||||||
],
|
context.l10n.connect_client_alert(clientOrigin),
|
||||||
),
|
style: const TextStyle(color: Colors.black),
|
||||||
backgroundColor: theme.colorScheme.primary,
|
|
||||||
showCloseIcon: true,
|
|
||||||
width: 350,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
scaffoldMessenger.showSnackBar(
|
|
||||||
SnackBar(
|
|
||||||
content: Row(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
SpotubeIcons.noWifi,
|
|
||||||
color: theme.colorScheme.onError,
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
|
||||||
Text(context.l10n.you_are_offline),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
backgroundColor: theme.colorScheme.error,
|
|
||||||
showCloseIcon: true,
|
|
||||||
width: 300,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
})
|
||||||
});
|
];
|
||||||
|
|
||||||
return () {
|
return () {
|
||||||
subscription.cancel();
|
for (final subscription in subscriptions) {
|
||||||
|
subscription.cancel();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,9 @@ import 'package:web_socket_channel/web_socket_channel.dart';
|
|||||||
import 'package:spotube/provider/volume_provider.dart';
|
import 'package:spotube/provider/volume_provider.dart';
|
||||||
|
|
||||||
final logger = getLogger('ConnectServer');
|
final logger = getLogger('ConnectServer');
|
||||||
|
final _connectClientStreamController = StreamController<String>.broadcast();
|
||||||
|
|
||||||
|
Stream<String> get connectClientStream => _connectClientStreamController.stream;
|
||||||
|
|
||||||
final connectServerProvider = FutureProvider((ref) async {
|
final connectServerProvider = FutureProvider((ref) async {
|
||||||
final enabled =
|
final enabled =
|
||||||
@ -45,169 +48,175 @@ final connectServerProvider = FutureProvider((ref) async {
|
|||||||
|
|
||||||
final subscriptions = <StreamSubscription>[];
|
final subscriptions = <StreamSubscription>[];
|
||||||
|
|
||||||
final websocket = webSocketHandler(
|
FutureOr<Response> websocket(Request req) => webSocketHandler(
|
||||||
(WebSocketChannel channel, String? protocol) async {
|
(WebSocketChannel channel, String? protocol) async {
|
||||||
ref.listen(
|
final context =
|
||||||
ProxyPlaylistNotifier.provider,
|
(req.context["shelf.io.connection_info"] as HttpConnectionInfo?);
|
||||||
(previous, next) {
|
final origin =
|
||||||
channel.sink.add(
|
"${context?.remoteAddress.host}:${context?.remotePort}";
|
||||||
WebSocketQueueEvent(next).toJson(),
|
_connectClientStreamController.add(origin);
|
||||||
);
|
|
||||||
},
|
|
||||||
fireImmediately: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// because audioPlayer events doesn't fireImmediately
|
ref.listen(
|
||||||
channel.sink.add(
|
ProxyPlaylistNotifier.provider,
|
||||||
WebSocketPlayingEvent(audioPlayer.isPlaying).toJson(),
|
(previous, next) {
|
||||||
);
|
channel.sink.add(
|
||||||
channel.sink.add(
|
WebSocketQueueEvent(next).toJson(),
|
||||||
WebSocketPositionEvent(await audioPlayer.position ?? Duration.zero)
|
|
||||||
.toJson(),
|
|
||||||
);
|
|
||||||
channel.sink.add(
|
|
||||||
WebSocketDurationEvent(await audioPlayer.duration ?? Duration.zero)
|
|
||||||
.toJson(),
|
|
||||||
);
|
|
||||||
channel.sink.add(
|
|
||||||
WebSocketShuffleEvent(await audioPlayer.isShuffled).toJson(),
|
|
||||||
);
|
|
||||||
channel.sink.add(
|
|
||||||
WebSocketLoopEvent(audioPlayer.loopMode).toJson(),
|
|
||||||
);
|
|
||||||
channel.sink.add(
|
|
||||||
WebSocketVolumeEvent(audioPlayer.volume).toJson(),
|
|
||||||
);
|
|
||||||
|
|
||||||
subscriptions.addAll([
|
|
||||||
audioPlayer.positionStream.listen(
|
|
||||||
(position) {
|
|
||||||
channel.sink.add(
|
|
||||||
WebSocketPositionEvent(position).toJson(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
audioPlayer.playingStream.listen(
|
|
||||||
(playing) {
|
|
||||||
channel.sink.add(
|
|
||||||
WebSocketPlayingEvent(playing).toJson(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
audioPlayer.durationStream.listen(
|
|
||||||
(duration) {
|
|
||||||
channel.sink.add(
|
|
||||||
WebSocketDurationEvent(duration).toJson(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
audioPlayer.shuffledStream.listen(
|
|
||||||
(shuffled) {
|
|
||||||
channel.sink.add(
|
|
||||||
WebSocketShuffleEvent(shuffled).toJson(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
audioPlayer.loopModeStream.listen(
|
|
||||||
(loopMode) {
|
|
||||||
channel.sink.add(
|
|
||||||
WebSocketLoopEvent(loopMode).toJson(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
audioPlayer.volumeStream.listen(
|
|
||||||
(volume) {
|
|
||||||
channel.sink.add(
|
|
||||||
WebSocketVolumeEvent(volume).toJson(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
channel.stream.listen(
|
|
||||||
(message) {
|
|
||||||
try {
|
|
||||||
final event = WebSocketEvent.fromJson(
|
|
||||||
jsonDecode(message),
|
|
||||||
(data) => data,
|
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
fireImmediately: true,
|
||||||
|
);
|
||||||
|
|
||||||
event.onLoad((event) async {
|
// because audioPlayer events doesn't fireImmediately
|
||||||
await playbackNotifier.load(
|
channel.sink.add(
|
||||||
event.data.tracks,
|
WebSocketPlayingEvent(audioPlayer.isPlaying).toJson(),
|
||||||
autoPlay: true,
|
);
|
||||||
initialIndex: event.data.initialIndex ?? 0,
|
channel.sink.add(
|
||||||
|
WebSocketPositionEvent(await audioPlayer.position ?? Duration.zero)
|
||||||
|
.toJson(),
|
||||||
|
);
|
||||||
|
channel.sink.add(
|
||||||
|
WebSocketDurationEvent(await audioPlayer.duration ?? Duration.zero)
|
||||||
|
.toJson(),
|
||||||
|
);
|
||||||
|
channel.sink.add(
|
||||||
|
WebSocketShuffleEvent(await audioPlayer.isShuffled).toJson(),
|
||||||
|
);
|
||||||
|
channel.sink.add(
|
||||||
|
WebSocketLoopEvent(audioPlayer.loopMode).toJson(),
|
||||||
|
);
|
||||||
|
channel.sink.add(
|
||||||
|
WebSocketVolumeEvent(audioPlayer.volume).toJson(),
|
||||||
|
);
|
||||||
|
|
||||||
|
subscriptions.addAll([
|
||||||
|
audioPlayer.positionStream.listen(
|
||||||
|
(position) {
|
||||||
|
channel.sink.add(
|
||||||
|
WebSocketPositionEvent(position).toJson(),
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
audioPlayer.playingStream.listen(
|
||||||
|
(playing) {
|
||||||
|
channel.sink.add(
|
||||||
|
WebSocketPlayingEvent(playing).toJson(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
audioPlayer.durationStream.listen(
|
||||||
|
(duration) {
|
||||||
|
channel.sink.add(
|
||||||
|
WebSocketDurationEvent(duration).toJson(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
audioPlayer.shuffledStream.listen(
|
||||||
|
(shuffled) {
|
||||||
|
channel.sink.add(
|
||||||
|
WebSocketShuffleEvent(shuffled).toJson(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
audioPlayer.loopModeStream.listen(
|
||||||
|
(loopMode) {
|
||||||
|
channel.sink.add(
|
||||||
|
WebSocketLoopEvent(loopMode).toJson(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
audioPlayer.volumeStream.listen(
|
||||||
|
(volume) {
|
||||||
|
channel.sink.add(
|
||||||
|
WebSocketVolumeEvent(volume).toJson(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
channel.stream.listen(
|
||||||
|
(message) {
|
||||||
|
try {
|
||||||
|
final event = WebSocketEvent.fromJson(
|
||||||
|
jsonDecode(message),
|
||||||
|
(data) => data,
|
||||||
|
);
|
||||||
|
|
||||||
if (event.data.collectionId != null) {
|
event.onLoad((event) async {
|
||||||
playbackNotifier.addCollection(event.data.collectionId!);
|
await playbackNotifier.load(
|
||||||
|
event.data.tracks,
|
||||||
|
autoPlay: true,
|
||||||
|
initialIndex: event.data.initialIndex ?? 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (event.data.collectionId != null) {
|
||||||
|
playbackNotifier.addCollection(event.data.collectionId!);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
event.onPause((event) async {
|
||||||
|
await audioPlayer.pause();
|
||||||
|
});
|
||||||
|
|
||||||
|
event.onResume((event) async {
|
||||||
|
await audioPlayer.resume();
|
||||||
|
});
|
||||||
|
|
||||||
|
event.onStop((event) async {
|
||||||
|
await audioPlayer.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
event.onNext((event) async {
|
||||||
|
await playbackNotifier.next();
|
||||||
|
});
|
||||||
|
|
||||||
|
event.onPrevious((event) async {
|
||||||
|
await playbackNotifier.previous();
|
||||||
|
});
|
||||||
|
|
||||||
|
event.onJump((event) async {
|
||||||
|
await playbackNotifier.jumpTo(event.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
event.onSeek((event) async {
|
||||||
|
await audioPlayer.seek(event.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
event.onShuffle((event) async {
|
||||||
|
await audioPlayer.setShuffle(event.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
event.onLoop((event) async {
|
||||||
|
await audioPlayer.setLoopMode(event.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
event.onAddTrack((event) async {
|
||||||
|
await playbackNotifier.addTrack(event.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
event.onRemoveTrack((event) async {
|
||||||
|
await playbackNotifier.removeTrack(event.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
event.onReorder((event) async {
|
||||||
|
await playbackNotifier.moveTrack(
|
||||||
|
event.data.oldIndex,
|
||||||
|
event.data.newIndex,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
event.onVolume((event) async {
|
||||||
|
ref.read(volumeProvider.notifier).setVolume(event.data);
|
||||||
|
});
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
Catcher2.reportCheckedError(e, stackTrace);
|
||||||
|
channel.sink.add(WebSocketErrorEvent(e.toString()).toJson());
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
onDone: () {
|
||||||
event.onPause((event) async {
|
logger.i('Connection closed');
|
||||||
await audioPlayer.pause();
|
},
|
||||||
});
|
),
|
||||||
|
]);
|
||||||
event.onResume((event) async {
|
},
|
||||||
await audioPlayer.resume();
|
)(req);
|
||||||
});
|
|
||||||
|
|
||||||
event.onStop((event) async {
|
|
||||||
await audioPlayer.stop();
|
|
||||||
});
|
|
||||||
|
|
||||||
event.onNext((event) async {
|
|
||||||
await playbackNotifier.next();
|
|
||||||
});
|
|
||||||
|
|
||||||
event.onPrevious((event) async {
|
|
||||||
await playbackNotifier.previous();
|
|
||||||
});
|
|
||||||
|
|
||||||
event.onJump((event) async {
|
|
||||||
await playbackNotifier.jumpTo(event.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
event.onSeek((event) async {
|
|
||||||
await audioPlayer.seek(event.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
event.onShuffle((event) async {
|
|
||||||
await audioPlayer.setShuffle(event.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
event.onLoop((event) async {
|
|
||||||
await audioPlayer.setLoopMode(event.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
event.onAddTrack((event) async {
|
|
||||||
await playbackNotifier.addTrack(event.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
event.onRemoveTrack((event) async {
|
|
||||||
await playbackNotifier.removeTrack(event.data);
|
|
||||||
});
|
|
||||||
|
|
||||||
event.onReorder((event) async {
|
|
||||||
await playbackNotifier.moveTrack(
|
|
||||||
event.data.oldIndex,
|
|
||||||
event.data.newIndex,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
event.onVolume((event) async {
|
|
||||||
ref.read(volumeProvider.notifier).setVolume(event.data);
|
|
||||||
});
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
Catcher2.reportCheckedError(e, stackTrace);
|
|
||||||
channel.sink.add(WebSocketErrorEvent(e.toString()).toJson());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDone: () {
|
|
||||||
logger.i('Connection closed');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
final port = Random().nextInt(17000) + 3000;
|
final port = Random().nextInt(17000) + 3000;
|
||||||
|
|
||||||
|
|||||||
@ -3,126 +3,144 @@
|
|||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
],
|
],
|
||||||
|
|
||||||
"bn": [
|
"bn": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ca": [
|
"ca": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
],
|
],
|
||||||
|
|
||||||
"de": [
|
"de": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
],
|
],
|
||||||
|
|
||||||
"es": [
|
"es": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fa": [
|
"fa": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fr": [
|
"fr": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
],
|
],
|
||||||
|
|
||||||
"hi": [
|
"hi": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
],
|
],
|
||||||
|
|
||||||
"it": [
|
"it": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ja": [
|
"ja": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ko": [
|
"ko": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ne": [
|
"ne": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
],
|
],
|
||||||
|
|
||||||
"nl": [
|
"nl": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
],
|
],
|
||||||
|
|
||||||
"pl": [
|
"pl": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
],
|
],
|
||||||
|
|
||||||
"pt": [
|
"pt": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ru": [
|
"ru": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
],
|
],
|
||||||
|
|
||||||
"tr": [
|
"tr": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
],
|
],
|
||||||
|
|
||||||
"uk": [
|
"uk": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
],
|
],
|
||||||
|
|
||||||
"vi": [
|
"vi": [
|
||||||
@ -131,13 +149,15 @@
|
|||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
],
|
],
|
||||||
|
|
||||||
"zh": [
|
"zh": [
|
||||||
"enable_connect",
|
"enable_connect",
|
||||||
"enable_connect_description",
|
"enable_connect_description",
|
||||||
"devices",
|
"devices",
|
||||||
"select"
|
"select",
|
||||||
|
"connect_client_alert"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user