From a06614bc5c27596056757fe82ab4cfc9772c7ec5 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sun, 27 Apr 2025 22:39:16 +0600 Subject: [PATCH] feat: add connect confirmation dialog --- lib/l10n/app_en.arb | 4 +- lib/l10n/generated/app_localizations.dart | 12 ++ lib/l10n/generated/app_localizations_ar.dart | 8 ++ lib/l10n/generated/app_localizations_bn.dart | 8 ++ lib/l10n/generated/app_localizations_ca.dart | 8 ++ lib/l10n/generated/app_localizations_cs.dart | 8 ++ lib/l10n/generated/app_localizations_de.dart | 8 ++ lib/l10n/generated/app_localizations_en.dart | 8 ++ lib/l10n/generated/app_localizations_es.dart | 8 ++ lib/l10n/generated/app_localizations_eu.dart | 8 ++ lib/l10n/generated/app_localizations_fa.dart | 8 ++ lib/l10n/generated/app_localizations_fi.dart | 8 ++ lib/l10n/generated/app_localizations_fr.dart | 8 ++ lib/l10n/generated/app_localizations_hi.dart | 8 ++ lib/l10n/generated/app_localizations_id.dart | 8 ++ lib/l10n/generated/app_localizations_it.dart | 8 ++ lib/l10n/generated/app_localizations_ja.dart | 8 ++ lib/l10n/generated/app_localizations_ka.dart | 8 ++ lib/l10n/generated/app_localizations_ko.dart | 8 ++ lib/l10n/generated/app_localizations_ne.dart | 8 ++ lib/l10n/generated/app_localizations_nl.dart | 8 ++ lib/l10n/generated/app_localizations_pl.dart | 8 ++ lib/l10n/generated/app_localizations_pt.dart | 8 ++ lib/l10n/generated/app_localizations_ru.dart | 8 ++ lib/l10n/generated/app_localizations_ta.dart | 8 ++ lib/l10n/generated/app_localizations_th.dart | 8 ++ lib/l10n/generated/app_localizations_tl.dart | 8 ++ lib/l10n/generated/app_localizations_tr.dart | 8 ++ lib/l10n/generated/app_localizations_uk.dart | 8 ++ lib/l10n/generated/app_localizations_vi.dart | 8 ++ lib/l10n/generated/app_localizations_zh.dart | 8 ++ .../playback/edit_connect_port_dialog.dart | 18 ++- lib/pages/connect/control/control.dart | 30 ++++- lib/provider/connect/connect.dart | 57 +++++++-- lib/provider/server/routes/connect.dart | 48 +++++++- untranslated_messages.json | 112 +++++++++++++----- 36 files changed, 471 insertions(+), 42 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 3ada81a2..4da423a8 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -426,5 +426,7 @@ "custom": "Custom", "add_custom_url": "Add custom URL", "edit_port": "Edit port", - "port_helper_msg": "Default is -1 which indicates random number. If you've firewall configured, setting this is recommended." + "port_helper_msg": "Default is -1 which indicates random number. If you've firewall configured, setting this is recommended.", + "connect_request": "Allow {client} to connect?", + "connection_request_denied": "Connection denied. User denied access." } diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 74015b8d..35cafab8 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -2716,6 +2716,18 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'** String get port_helper_msg; + + /// No description provided for @connect_request. + /// + /// In en, this message translates to: + /// **'Allow {client} to connect?'** + String connect_request(Object client); + + /// No description provided for @connection_request_denied. + /// + /// In en, this message translates to: + /// **'Connection denied. User denied access.'** + String get connection_request_denied; } class _AppLocalizationsDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/generated/app_localizations_ar.dart b/lib/l10n/generated/app_localizations_ar.dart index bb7475a1..70730ad7 100644 --- a/lib/l10n/generated/app_localizations_ar.dart +++ b/lib/l10n/generated/app_localizations_ar.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsAr extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_bn.dart b/lib/l10n/generated/app_localizations_bn.dart index b9250190..25edc5b7 100644 --- a/lib/l10n/generated/app_localizations_bn.dart +++ b/lib/l10n/generated/app_localizations_bn.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsBn extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_ca.dart b/lib/l10n/generated/app_localizations_ca.dart index f28e71b8..9c11dbf4 100644 --- a/lib/l10n/generated/app_localizations_ca.dart +++ b/lib/l10n/generated/app_localizations_ca.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsCa extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_cs.dart b/lib/l10n/generated/app_localizations_cs.dart index bb11fadf..fdb76e0c 100644 --- a/lib/l10n/generated/app_localizations_cs.dart +++ b/lib/l10n/generated/app_localizations_cs.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsCs extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 674af57b..28f1aa2a 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsDe extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 637d3ec2..91ac3586 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsEn extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_es.dart b/lib/l10n/generated/app_localizations_es.dart index 350918d9..eb8f4524 100644 --- a/lib/l10n/generated/app_localizations_es.dart +++ b/lib/l10n/generated/app_localizations_es.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsEs extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_eu.dart b/lib/l10n/generated/app_localizations_eu.dart index 4715493f..2e59b291 100644 --- a/lib/l10n/generated/app_localizations_eu.dart +++ b/lib/l10n/generated/app_localizations_eu.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsEu extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_fa.dart b/lib/l10n/generated/app_localizations_fa.dart index c356108a..f88063b1 100644 --- a/lib/l10n/generated/app_localizations_fa.dart +++ b/lib/l10n/generated/app_localizations_fa.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsFa extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_fi.dart b/lib/l10n/generated/app_localizations_fi.dart index 76bd779f..2d49313c 100644 --- a/lib/l10n/generated/app_localizations_fi.dart +++ b/lib/l10n/generated/app_localizations_fi.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsFi extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_fr.dart b/lib/l10n/generated/app_localizations_fr.dart index bee73abd..4ba85bc9 100644 --- a/lib/l10n/generated/app_localizations_fr.dart +++ b/lib/l10n/generated/app_localizations_fr.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsFr extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_hi.dart b/lib/l10n/generated/app_localizations_hi.dart index 8c506afd..5977f066 100644 --- a/lib/l10n/generated/app_localizations_hi.dart +++ b/lib/l10n/generated/app_localizations_hi.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsHi extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_id.dart b/lib/l10n/generated/app_localizations_id.dart index dc4e620a..c18110e7 100644 --- a/lib/l10n/generated/app_localizations_id.dart +++ b/lib/l10n/generated/app_localizations_id.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsId extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_it.dart b/lib/l10n/generated/app_localizations_it.dart index 9ce94d3c..f434b453 100644 --- a/lib/l10n/generated/app_localizations_it.dart +++ b/lib/l10n/generated/app_localizations_it.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsIt extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_ja.dart b/lib/l10n/generated/app_localizations_ja.dart index 869a4695..e31623d1 100644 --- a/lib/l10n/generated/app_localizations_ja.dart +++ b/lib/l10n/generated/app_localizations_ja.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsJa extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_ka.dart b/lib/l10n/generated/app_localizations_ka.dart index 24592ff9..2b6911ff 100644 --- a/lib/l10n/generated/app_localizations_ka.dart +++ b/lib/l10n/generated/app_localizations_ka.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsKa extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_ko.dart b/lib/l10n/generated/app_localizations_ko.dart index 945d95c9..5cc03f21 100644 --- a/lib/l10n/generated/app_localizations_ko.dart +++ b/lib/l10n/generated/app_localizations_ko.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsKo extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_ne.dart b/lib/l10n/generated/app_localizations_ne.dart index f96b1b7b..677e0c2d 100644 --- a/lib/l10n/generated/app_localizations_ne.dart +++ b/lib/l10n/generated/app_localizations_ne.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsNe extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_nl.dart b/lib/l10n/generated/app_localizations_nl.dart index 127a5e1a..2e33cf33 100644 --- a/lib/l10n/generated/app_localizations_nl.dart +++ b/lib/l10n/generated/app_localizations_nl.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsNl extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_pl.dart b/lib/l10n/generated/app_localizations_pl.dart index 7d9cdc7a..f8b9473d 100644 --- a/lib/l10n/generated/app_localizations_pl.dart +++ b/lib/l10n/generated/app_localizations_pl.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsPl extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_pt.dart b/lib/l10n/generated/app_localizations_pt.dart index 320f0023..a335d39d 100644 --- a/lib/l10n/generated/app_localizations_pt.dart +++ b/lib/l10n/generated/app_localizations_pt.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsPt extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_ru.dart b/lib/l10n/generated/app_localizations_ru.dart index 731757f9..f40680bd 100644 --- a/lib/l10n/generated/app_localizations_ru.dart +++ b/lib/l10n/generated/app_localizations_ru.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsRu extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_ta.dart b/lib/l10n/generated/app_localizations_ta.dart index 26fa78f3..d8f1cb3d 100644 --- a/lib/l10n/generated/app_localizations_ta.dart +++ b/lib/l10n/generated/app_localizations_ta.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsTa extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_th.dart b/lib/l10n/generated/app_localizations_th.dart index bc6c3396..3ed7e249 100644 --- a/lib/l10n/generated/app_localizations_th.dart +++ b/lib/l10n/generated/app_localizations_th.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsTh extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_tl.dart b/lib/l10n/generated/app_localizations_tl.dart index d383f949..f3a477c4 100644 --- a/lib/l10n/generated/app_localizations_tl.dart +++ b/lib/l10n/generated/app_localizations_tl.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsTl extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_tr.dart b/lib/l10n/generated/app_localizations_tr.dart index 5818e55b..cbbd82d0 100644 --- a/lib/l10n/generated/app_localizations_tr.dart +++ b/lib/l10n/generated/app_localizations_tr.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsTr extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_uk.dart b/lib/l10n/generated/app_localizations_uk.dart index fff23c41..0c3961ce 100644 --- a/lib/l10n/generated/app_localizations_uk.dart +++ b/lib/l10n/generated/app_localizations_uk.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsUk extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_vi.dart b/lib/l10n/generated/app_localizations_vi.dart index 160b522f..ea4e7fe3 100644 --- a/lib/l10n/generated/app_localizations_vi.dart +++ b/lib/l10n/generated/app_localizations_vi.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsVi extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_zh.dart b/lib/l10n/generated/app_localizations_zh.dart index a1b400e7..d328316a 100644 --- a/lib/l10n/generated/app_localizations_zh.dart +++ b/lib/l10n/generated/app_localizations_zh.dart @@ -1377,4 +1377,12 @@ class AppLocalizationsZh extends AppLocalizations { @override String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + + @override + String connect_request(Object client) { + return 'Allow $client to connect?'; + } + + @override + String get connection_request_denied => 'Connection denied. User denied access.'; } diff --git a/lib/modules/settings/playback/edit_connect_port_dialog.dart b/lib/modules/settings/playback/edit_connect_port_dialog.dart index a531aa3a..587f4388 100644 --- a/lib/modules/settings/playback/edit_connect_port_dialog.dart +++ b/lib/modules/settings/playback/edit_connect_port_dialog.dart @@ -38,7 +38,23 @@ class SettingsPlaybackEditConnectPortDialog extends HookConsumerWidget { validator: FormBuilderValidators.integer(radix: 10), keyboardType: TextInputType.number, inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, + // Allow only signed integers + TextInputFormatter.withFunction( + (oldValue, newValue) { + if (newValue.text.isEmpty) { + return const TextEditingValue(); + } + if (newValue.text.length == 1 && newValue.text == "-") { + return newValue; + } + + final intValue = int.tryParse(newValue.text); + if (intValue == null) { + return oldValue; + } + return newValue; + }, + ), ], ), const Gap(5), diff --git a/lib/pages/connect/control/control.dart b/lib/pages/connect/control/control.dart index 1c3e76c8..e28566fd 100644 --- a/lib/pages/connect/control/control.dart +++ b/lib/pages/connect/control/control.dart @@ -1,9 +1,13 @@ +import 'dart:convert'; + import 'package:auto_route/auto_route.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart' hide Consumer; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/modules/player/player_queue.dart'; import 'package:spotube/modules/player/volume_slider.dart'; import 'package:spotube/components/image/universal_image.dart'; @@ -57,6 +61,7 @@ class ConnectControlPage extends HookConsumerWidget { final resolvedService = ref.watch(connectClientsProvider).asData?.value.resolvedService; + final connect = ref.watch(connectProvider); final connectNotifier = ref.read(connectProvider.notifier); final playlist = ref.watch(queueProvider); final playing = ref.watch(playingProvider); @@ -69,12 +74,32 @@ class ConnectControlPage extends HookConsumerWidget { } }); + useEffect(() { + if (connect.asData?.value == null) return null; + + final subscription = connect.asData?.value?.stream.listen((message) { + final event = WebSocketEvent.fromJson( + jsonDecode(message), + (data) => data, + ); + event.onError((event) { + if (event.data != "Connection denied") return; + if (!context.mounted) return; + context.back(); + }); + }); + + return () { + subscription?.cancel(); + }; + }, [connect.asData?.value]); + return SafeArea( bottom: false, child: Scaffold( headers: [ TitleBar( - title: Text(resolvedService!.name), + title: Text(resolvedService?.name ?? ""), ) ], child: LayoutBuilder(builder: (context, constrains) { @@ -247,7 +272,8 @@ class ConnectControlPage extends HookConsumerWidget { ), Tooltip( tooltip: TooltipContainer( - child: Text(context.l10n.next_track)).call, + child: Text(context.l10n.next_track)) + .call, child: IconButton.ghost( icon: const Icon(SpotubeIcons.skipForward), onPressed: playlist.activeTrack == null diff --git a/lib/provider/connect/connect.dart b/lib/provider/connect/connect.dart index 000a28af..93d2fb88 100644 --- a/lib/provider/connect/connect.dart +++ b/lib/provider/connect/connect.dart @@ -1,6 +1,10 @@ import 'dart:convert'; import 'package:media_kit/media_kit.dart' hide Track; +import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/collections/routes.dart'; +import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/audio_player/state.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/logger/logger.dart'; @@ -46,15 +50,17 @@ final volumeProvider = StateProvider( (ref) => 1.0, ); -class ConnectNotifier extends AsyncNotifier { +typedef ConnectState = ({WebSocketChannel channel, Stream stream}); + +class ConnectNotifier extends AsyncNotifier { @override build() async { try { - final connectClients = ref.watch(connectClientsProvider); + final connectClients = await ref.watch(connectClientsProvider.future); - if (connectClients.asData?.value.resolvedService == null) return null; + if (connectClients.resolvedService == null) return null; - final service = connectClients.asData!.value.resolvedService!; + final service = connectClients.resolvedService!; AppLogger.log.t( '♾️ Connecting to ${service.name}: ws://${service.host}:${service.port}/ws', @@ -70,7 +76,9 @@ class ConnectNotifier extends AsyncNotifier { '✅ Connected to ${service.name}: ws://${service.host}:${service.port}/ws', ); - final subscription = channel.stream.listen( + final stream = channel.stream.asBroadcastStream(); + + final subscription = stream.listen( (message) { final event = WebSocketEvent.fromJson(jsonDecode(message), (data) => data); @@ -102,6 +110,38 @@ class ConnectNotifier extends AsyncNotifier { event.onVolume((event) { ref.read(volumeProvider.notifier).state = event.data; }); + + event.onError((event) { + if (event.data == "Connection denied") { + ref.read(connectClientsProvider.notifier).clearResolvedService(); + + if (rootNavigatorKey.currentContext?.mounted == true) { + final theme = Theme.of(rootNavigatorKey.currentContext!); + + showToast( + context: rootNavigatorKey.currentContext!, + location: ToastLocation.topRight, + dismissible: true, + builder: (context, overlay) { + return SurfaceCard( + fillColor: theme.colorScheme.destructive, + filled: true, + child: Basic( + leading: const Icon(SpotubeIcons.error), + title: Text( + context.l10n.connection_request_denied, + style: theme.typography.normal.copyWith( + color: theme.colorScheme.destructiveForeground, + ), + ), + leadingAlignment: Alignment.center, + ), + ); + }, + ); + } + } + }); }, onError: (error) { AppLogger.reportError(error, StackTrace.current); @@ -113,7 +153,7 @@ class ConnectNotifier extends AsyncNotifier { channel.sink.close(status.goingAway); }); - return channel; + return (channel: channel, stream: stream); } catch (e, stack) { AppLogger.reportError(e, stack); rethrow; @@ -122,7 +162,7 @@ class ConnectNotifier extends AsyncNotifier { Future emit(Object message) async { if (state.value == null) return; - state.value?.sink.add( + state.value?.channel.sink.add( message is String ? message : (message as dynamic).toJson(), ); } @@ -184,7 +224,6 @@ class ConnectNotifier extends AsyncNotifier { } } -final connectProvider = - AsyncNotifierProvider( +final connectProvider = AsyncNotifierProvider( () => ConnectNotifier(), ); diff --git a/lib/provider/server/routes/connect.dart b/lib/provider/server/routes/connect.dart index 0d35b473..6c4d8ce0 100644 --- a/lib/provider/server/routes/connect.dart +++ b/lib/provider/server/routes/connect.dart @@ -3,9 +3,12 @@ import 'dart:convert'; import 'dart:io'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shelf/shelf.dart'; import 'package:shelf_web_socket/shelf_web_socket.dart'; import 'package:spotify/spotify.dart'; +import 'package:spotube/collections/routes.dart'; +import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; @@ -43,6 +46,8 @@ class ServerConnectRoutes { Stream get connectClientStream => _connectClientStreamController.stream; + final List _allowedConnections = []; + FutureOr websocket(Request req) { return webSocketHandler( ( @@ -54,6 +59,47 @@ class ServerConnectRoutes { final origin = "${context?.remoteAddress.host}:${context?.remotePort}"; _connectClientStreamController.add(origin); + // Confirm whether user allows to connect + if (rootNavigatorKey.currentContext?.mounted == true && + _allowedConnections.contains(origin) == false) { + final confirmed = await showDialog( + context: rootNavigatorKey.currentContext!, + builder: (context) { + return AlertDialog( + title: Text(context.l10n.connect), + content: Text( + context.l10n.connect_request(origin), + ), + actions: [ + Button.secondary( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: Text(context.l10n.decline), + ), + Button.primary( + onPressed: () { + Navigator.of(context).pop(true); + }, + child: Text(context.l10n.accept), + ), + ], + ); + }, + ) ?? + false; + + if (confirmed) { + _allowedConnections.add(origin); + } else { + channel.sink.addEvent( + WebSocketErrorEvent("Connection denied"), + ); + await channel.sink.close(); + return; + } + } + ref.listen( audioPlayerProvider, (previous, next) { @@ -106,7 +152,7 @@ class ServerConnectRoutes { }, ), channel.stream.listen( - (message) { + (message) async { try { final event = WebSocketEvent.fromJson( jsonDecode(message), diff --git a/untranslated_messages.json b/untranslated_messages.json index 820358af..81ba7941 100644 --- a/untranslated_messages.json +++ b/untranslated_messages.json @@ -1,141 +1,197 @@ { "ar": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "bn": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "ca": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "cs": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "de": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "es": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "eu": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "fa": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "fi": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "fr": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "hi": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "id": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "it": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "ja": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "ka": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "ko": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "ne": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "nl": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "pl": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "pt": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "ru": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "ta": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "th": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "tl": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "tr": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "uk": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "vi": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ], "zh": [ "edit_port", - "port_helper_msg" + "port_helper_msg", + "connect_request", + "connection_request_denied" ] }