Merge pull request #2687 from KRTirtho/fix/path-traversal-through-websocket

fix: path traversal through websocket
This commit is contained in:
Kingkor Roy Tirtho 2025-04-27 23:19:19 +06:00 committed by GitHub
commit 76f0f18592
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
79 changed files with 5681 additions and 1049 deletions

File diff suppressed because one or more lines are too long

View File

@ -15,8 +15,7 @@ class TextFormBuilderField extends StatelessWidget {
// final AlignmentGeometry? leadingAlignment;
// final AlignmentGeometry? trailingAlignment;
final bool border;
final Widget? leading;
final Widget? trailing;
final List<InputFeature> features;
final EdgeInsetsGeometry? padding;
final ValueChanged<String>? onSubmitted;
final VoidCallback? onEditingComplete;
@ -63,8 +62,6 @@ class TextFormBuilderField extends StatelessWidget {
this.filled = false,
this.placeholder,
this.border = true,
this.leading,
this.trailing,
this.padding,
this.onSubmitted,
this.onEditingComplete,
@ -96,6 +93,7 @@ class TextFormBuilderField extends StatelessWidget {
// this.leadingAlignment,
// this.trailingAlignment,
this.statesController,
this.features = const [],
});
@override
@ -130,10 +128,7 @@ class TextFormBuilderField extends StatelessWidget {
filled: filled,
placeholder: placeholder,
border: border,
features: [
if (leading != null) InputFeature.leading(leading!),
if (trailing != null) InputFeature.trailing(trailing!),
],
features: features,
padding: padding,
onSubmitted: (value) {
field.validate();

View File

@ -426,5 +426,9 @@
"download": "تنزيل",
"file_not_found": "الملف غير موجود",
"custom": "مخصص",
"add_custom_url": "إضافة URL مخصص"
"add_custom_url": "إضافة URL مخصص",
"edit_port": "تعديل المنفذ",
"port_helper_msg": "القيمة الافتراضية هي -1 والتي تشير إلى رقم عشوائي. إذا كان لديك جدار ناري مُعد، يُوصى بتعيين هذا.",
"connect_request": "السماح لـ {client} بالاتصال؟",
"connection_request_denied": "تم رفض الاتصال. المستخدم رفض الوصول."
}

View File

@ -426,5 +426,9 @@
"download": "ডাউনলোড",
"file_not_found": "ফাইল পাওয়া যায়নি",
"custom": "কাস্টম",
"add_custom_url": "কাস্টম URL যোগ করুন"
"add_custom_url": "কাস্টম URL যোগ করুন",
"edit_port": "পোর্ট সম্পাদনা করুন",
"port_helper_msg": "ডিফল্ট হল -1 যা এলোমেলো সংখ্যা নির্দেশ করে। যদি আপনার ফায়ারওয়াল কনফিগার করা থাকে, তবে এটি সেট করা সুপারিশ করা হয়।",
"connect_request": "{client} কে সংযোগ করতে অনুমতি দেবেন?",
"connection_request_denied": "সংযোগ অস্বীকৃত। ব্যবহারকারী প্রবেশাধিকার অস্বীকার করেছে।"
}

View File

@ -426,5 +426,9 @@
"download": "Descarregar",
"file_not_found": "Fitxer no trobat",
"custom": "Personalitzat",
"add_custom_url": "Afegir URL personalitzada"
"add_custom_url": "Afegir URL personalitzada",
"edit_port": "Editar port",
"port_helper_msg": "El valor per defecte és -1, que indica un número aleatori. Si teniu un tallafoc configurat, es recomana establir-ho.",
"connect_request": "Permetre que {client} es connecti?",
"connection_request_denied": "Connexió denegada. L'usuari ha denegat l'accés."
}

View File

@ -426,5 +426,9 @@
"download": "Stáhnout",
"file_not_found": "Soubor nenalezen",
"custom": "Vlastní",
"add_custom_url": "Přidat vlastní URL"
"add_custom_url": "Přidat vlastní URL",
"edit_port": "Upravit port",
"port_helper_msg": "Výchozí hodnota je -1, což znamená náhodné číslo. Pokud máte nakonfigurován firewall, doporučuje se to nastavit.",
"connect_request": "Povolit {client} připojení?",
"connection_request_denied": "Připojení bylo zamítnuto. Uživatel odmítl přístup."
}

View File

@ -426,5 +426,9 @@
"download": "Herunterladen",
"file_not_found": "Datei nicht gefunden",
"custom": "Benutzerdefiniert",
"add_custom_url": "Benutzerdefinierte URL hinzufügen"
"add_custom_url": "Benutzerdefinierte URL hinzufügen",
"edit_port": "Port bearbeiten",
"port_helper_msg": "Der Standardwert ist -1, was eine zufällige Zahl bedeutet. Wenn Sie eine Firewall konfiguriert haben, wird empfohlen, dies einzustellen.",
"connect_request": "{client} die Verbindung erlauben?",
"connection_request_denied": "Verbindung abgelehnt. Benutzer hat den Zugriff verweigert."
}

View File

@ -424,5 +424,9 @@
"download": "Download",
"file_not_found": "File not found",
"custom": "Custom",
"add_custom_url": "Add custom URL"
"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.",
"connect_request": "Allow {client} to connect?",
"connection_request_denied": "Connection denied. User denied access."
}

View File

@ -426,5 +426,9 @@
"download": "Descargar",
"file_not_found": "Archivo no encontrado",
"custom": "Personalizado",
"add_custom_url": "Agregar URL personalizada"
"add_custom_url": "Agregar URL personalizada",
"edit_port": "Editar puerto",
"port_helper_msg": "El valor predeterminado es -1, lo que indica un número aleatorio. Si tienes un firewall configurado, se recomienda establecer esto.",
"connect_request": "¿Permitir que {client} se conecte?",
"connection_request_denied": "Conexión denegada. El usuario denegó el acceso."
}

View File

@ -426,5 +426,9 @@
"download": "Deskargatu",
"file_not_found": "Fitxategia ez da aurkitu",
"custom": "Pertsonalizatua",
"add_custom_url": "Gehitu URL pertsonalizatua"
"add_custom_url": "Gehitu URL pertsonalizatua",
"edit_port": "Editatu portua",
"port_helper_msg": "Lehenetsitako balioa -1 da, zenbaki aleatorioa adierazten duena. Su firewall konfiguratu baduzu, gomendatzen da hau ezartzea.",
"connect_request": "{client} konektatzea baimendu?",
"connection_request_denied": "Konektatzea ukatu da. Erabiltzaileak sarbidea ukatu du."
}

View File

@ -426,5 +426,9 @@
"download": "دانلود",
"file_not_found": "فایل پیدا نشد",
"custom": "شخصی‌سازی شده",
"add_custom_url": "اضافه کردن URL سفارشی"
"add_custom_url": "اضافه کردن URL سفارشی",
"edit_port": "ویرایش پورت",
"port_helper_msg": "پیش‌فرض -1 است که نشان‌دهنده یک عدد تصادفی است. اگر فایروال شما پیکربندی شده است، توصیه می‌شود این را تنظیم کنید.",
"connect_request": "آیا اجازه می‌دهید {client} متصل شود؟",
"connection_request_denied": "اتصال رد شد. کاربر دسترسی را رد کرد."
}

View File

@ -426,5 +426,9 @@
"download": "Lataa",
"file_not_found": "Tiedostoa ei löydy",
"custom": "Mukautettu",
"add_custom_url": "Lisää mukautettu URL"
"add_custom_url": "Lisää mukautettu URL",
"edit_port": "Muokkaa porttia",
"port_helper_msg": "Oletusarvo on -1, mikä tarkoittaa satunnaista numeroa. Jos sinulla on palomuuri määritetty, tämän asettamista suositellaan.",
"connect_request": "Salli {client} yhdistää?",
"connection_request_denied": "Yhteys evätty. Käyttäjä eväsi pääsyn."
}

View File

@ -426,5 +426,9 @@
"download": "Télécharger",
"file_not_found": "Fichier non trouvé",
"custom": "Personnalisé",
"add_custom_url": "Ajouter une URL personnalisée"
"add_custom_url": "Ajouter une URL personnalisée",
"edit_port": "Modifier le port",
"port_helper_msg": "La valeur par défaut est -1, ce qui indique un nombre aléatoire. Si vous avez configuré un pare-feu, il est recommandé de le définir.",
"connect_request": "Autoriser {client} à se connecter ?",
"connection_request_denied ": "Connexion refusée. L'utilisateur a refusé l'accès."
}

View File

@ -426,5 +426,9 @@
"download": "डाउनलोड करें",
"file_not_found": "फाइल नहीं मिली",
"custom": "कस्टम",
"add_custom_url": "कस्टम URL जोड़ें"
"add_custom_url": "कस्टम URL जोड़ें",
"edit_port": "पोर्ट संपादित करें",
"port_helper_msg": "डिफ़ॉल्ट -1 है जो यादृच्छिक संख्या को दर्शाता है। यदि आपने फ़ायरवॉल कॉन्फ़िगर किया है, तो इसे सेट करना अनुशंसित है।",
"connect_request": "{client} को कनेक्ट करने की अनुमति दें?",
"connection_request_denied": "कनेक्शन अस्वीकृत। उपयोगकर्ता ने पहुंच अस्वीकृत कर दी।"
}

View File

@ -426,5 +426,9 @@
"download": "Download",
"file_not_found": "File not found",
"custom": "Custom",
"add_custom_url": "Add custom URL"
"add_custom_url": "Add custom URL",
"edit_port": "Edit port",
"port_helper_msg": "Default adalah -1 yang menunjukkan angka acak. Jika Anda telah mengonfigurasi firewall, disarankan untuk mengatur ini.",
"connect_request": "Izinkan {client} untuk terhubung?",
"connection_request_denied": "Koneksi ditolak. Pengguna menolak akses."
}

View File

@ -427,5 +427,9 @@
"download": "Scarica",
"file_not_found": "File non trovato",
"custom": "Personalizzato",
"add_custom_url": "Aggiungi URL personalizzato"
"add_custom_url": "Aggiungi URL personalizzato",
"edit_port": "Modifica porta",
"port_helper_msg": "Il valore predefinito è -1, che indica un numero casuale. Se hai configurato un firewall, si consiglia di impostarlo.",
"connect_request": "Consentire a {client} di connettersi?",
"connection_request_denied": "Connessione negata. L'utente ha negato l'accesso."
}

View File

@ -426,5 +426,9 @@
"download": "ダウンロード",
"file_not_found": "ファイルが見つかりません",
"custom": "カスタム",
"add_custom_url": "カスタムURLを追加"
"add_custom_url": "カスタムURLを追加",
"edit_port": "ポートを編集",
"port_helper_msg": "デフォルトは-1で、ランダムな番号を示します。ファイアウォールを設定している場合は、これを設定することをお勧めします。",
"connect_request": "{client}の接続を許可しますか?",
"connection_request_denied": "接続が拒否されました。ユーザーがアクセスを拒否しました。"
}

View File

@ -426,5 +426,9 @@
"download": "ჩამოტვირთვა",
"file_not_found": "ფაილი ვერ მოიძებნა",
"custom": "პერსონალიზირებული",
"add_custom_url": "დამატება პერსონალური URL"
"add_custom_url": "დამატება პერსონალური URL",
"edit_port": "პორტის რედაქტირება",
"port_helper_msg": "ნაგულისხმევი არის -1, რაც შემთხვევითი ნომრის მითითებას ნიშნავს. თუ لديك firewall настроен, рекомендуется установить это.",
"connect_request": "{client}-ის დაკავშირების ნებართვა?",
"connection_request_denied": "კავშირი უარყოფილია. მომხმარებელმა უარყო წვდომა."
}

View File

@ -427,5 +427,9 @@
"download": "다운로드",
"file_not_found": "파일을 찾을 수 없습니다",
"custom": "사용자 정의",
"add_custom_url": "사용자 정의 URL 추가"
"add_custom_url": "사용자 정의 URL 추가",
"edit_port": "포트 편집",
"port_helper_msg": "기본값은 -1로 무작위 숫자를 나타냅니다. 방화벽이 구성된 경우 이를 설정하는 것이 좋습니다.",
"connect_request": "{client}의 연결을 허용하시겠습니까?",
"connection_request_denied": "연결이 거부되었습니다. 사용자가 액세스를 거부했습니다."
}

View File

@ -426,5 +426,9 @@
"download": "डाउनलोड",
"file_not_found": "फ़ाइल नहीं मिली",
"custom": "कस्टम",
"add_custom_url": "कस्टम URL जोड़ें"
"add_custom_url": "कस्टम URL जोड़ें",
"edit_port": "पोर्ट सम्पादन गर्नुहोस्",
"port_helper_msg": "डिफ़ॉल्ट -1 हो जुन यादृच्छिक संख्या जनाउँछ। यदि तपाईंले फायरवाल कन्फिगर गर्नुभएको छ भने, यसलाई सेट गर्न सिफारिस गरिन्छ।",
"connect_request": "{client} लाई जडान गर्न अनुमति दिनुहोस्?",
"connection_request_denied": "जडान अस्वीकृत। प्रयोगकर्ताले पहुँच अस्वीकृत गर्यो।"
}

View File

@ -427,5 +427,9 @@
"download": "Downloaden",
"file_not_found": "Bestand niet gevonden",
"custom": "Aangepast",
"add_custom_url": "Voeg aangepaste URL toe"
"add_custom_url": "Voeg aangepaste URL toe",
"edit_port": "Poort bewerken",
"port_helper_msg": "Standaard is -1, wat een willekeurig nummer aangeeft. Als je een firewall hebt geconfigureerd, wordt aanbevolen dit in te stellen.",
"connect_request": "Toestaan dat {client} verbinding maakt?",
"connection_request_denied": "Verbinding geweigerd. Gebruiker heeft toegang geweigerd."
}

View File

@ -426,5 +426,9 @@
"download": "Pobierz",
"file_not_found": "Plik nie znaleziony",
"custom": "Niestandardowy",
"add_custom_url": "Dodaj niestandardowy URL"
"add_custom_url": "Dodaj niestandardowy URL",
"edit_port": "Edytuj port",
"port_helper_msg": "Domyślna wartość to -1, co oznacza losową liczbę. Jeśli masz skonfigurowany zaporę, zaleca się jej ustawienie.",
"connect_request": "Zezwolić {client} na połączenie?",
"connection_request_denied": "Połączenie odrzucone. Użytkownik odmówił dostępu."
}

View File

@ -426,5 +426,9 @@
"download": "Baixar",
"file_not_found": "Arquivo não encontrado",
"custom": "Personalizado",
"add_custom_url": "Adicionar URL personalizada"
"add_custom_url": "Adicionar URL personalizada",
"edit_port": "Editar porta",
"port_helper_msg": "O padrão é -1, que indica um número aleatório. Se você tiver um firewall configurado, é recomendável definir isso.",
"connect_request": "Permitir que {client} se conecte?",
"connection_request_denied": "Conexão negada. O usuário negou o acesso ."
}

View File

@ -426,5 +426,9 @@
"download": "Скачать",
"file_not_found": "Файл не найден",
"custom": "Пользовательский",
"add_custom_url": "Добавить пользовательский URL"
"add_custom_url": "Добавить пользовательский URL",
"edit_port": "Редактировать порт",
"port_helper_msg": "По умолчанию -1, что означает случайное число. Если у вас настроен брандмауэр, рекомендуется установить это.",
"connect_request": "Разрешить {client} подключение?",
"connection_request_denied": "Подключение отклонено. Пользователь отказал в доступе."
}

View File

@ -424,5 +424,9 @@
"download": "பதிவிறக்கு",
"file_not_found": "கோப்பு கிடைக்கவில்லை",
"custom": "தனிப்பயன்",
"add_custom_url": "தனிப்பயன் URL ஐச் சேர்க்கவும்"
"add_custom_url": "தனிப்பயன் URL ஐச் சேர்க்கவும்",
"edit_port": "போர்டு திருத்தவும்",
"port_helper_msg": "இயல்புநிலை -1 ஆகும், இது சீரற்ற எண்ணை குறிக்கிறது. நீங்கள் தீயணைப்பு அமைக்கப்பட்டிருந்தால், இதை அமைப்பது பரிந்துரைக்கப்படுகிறது.",
"connect_request": "{client} க்கு இணைக்க அனுமதிக்கவா?",
"connection_request_denied": "இணைப்பு மறுக்கப்பட்டது. பயனர் அணுகலை மறுத்தார்."
}

View File

@ -427,5 +427,9 @@
"download": "ดาวน์โหลด",
"file_not_found": "ไม่พบไฟล์",
"custom": "กำหนดเอง",
"add_custom_url": "เพิ่ม URL แบบกำหนดเอง"
"add_custom_url": "เพิ่ม URL แบบกำหนดเอง",
"edit_port": "แก้ไขพอร์ต",
"port_helper_msg": "ค่าเริ่มต้นคือ -1 ซึ่งหมายถึงหมายเลขสุ่ม หากคุณได้กำหนดค่าไฟร์วอลล์แล้ว แนะนำให้ตั้งค่านี้",
"connect_request": "อนุญาตให้ {client} เชื่อมต่อหรือไม่?",
"connection_request_denied": "การเชื่อมต่อล้มเหลว ผู้ใช้ปฏิเสธการเข้าถึง"
}

View File

@ -424,5 +424,9 @@
"download": "I-download",
"file_not_found": "Hindi nahanap ang file",
"custom": "Custom",
"add_custom_url": "Magdagdag ng custom URL"
"add_custom_url": "Magdagdag ng custom URL",
"edit_port": "I-edit ang port",
"port_helper_msg": "Ang default ay -1 na nagpapahiwatig ng random na numero. Kung na-configure mo ang firewall, inirerekomenda na itakda ito.",
"connect_request": "Payagan ang {client} na kumonekta?",
"connection_request_denied": "Tanggihan ang koneksyon. Tinanggihan ng gumagamit ang pag-access."
}

View File

@ -426,5 +426,9 @@
"download": "İndir",
"file_not_found": "Dosya bulunamadı",
"custom": "Özel",
"add_custom_url": "Özel URL ekle"
"add_custom_url": "Özel URL ekle",
"edit_port": "Portu düzenle",
"port_helper_msg": "Varsayılan -1'dir, bu da rastgele bir sayıyı gösterir. Bir güvenlik duvarınız varsa, bunu ayarlamanız önerilir.",
"connect_request": "{client} bağlantısına izin verilsin mi?",
"connection_request_denied": "Bağlantı reddedildi. Kullanıcı erişimi reddetti."
}

View File

@ -426,5 +426,9 @@
"download": "Завантажити",
"file_not_found": "Файл не знайдено",
"custom": "Користувацький",
"add_custom_url": "Додати користувацький URL"
"add_custom_url": "Додати користувацький URL",
"edit_port": "Редагувати порт",
"port_helper_msg": "За замовчуванням -1, що означає випадкове число. Якщо у вас налаштований брандмауер, рекомендується це налаштувати.",
"connect_request": "Дозволити {client} підключення?",
"connection_request_denied": "Підключення відхилено. Користувач відмовив у доступі."
}

View File

@ -426,5 +426,9 @@
"download": "Tải xuống",
"file_not_found": "Không tìm thấy tệp",
"custom": "Tùy chỉnh",
"add_custom_url": "Thêm URL tùy chỉnh"
"add_custom_url": "Thêm URL tùy chỉnh",
"edit_port": "Chỉnh sửa cổng",
"port_helper_msg": "Mặc định là -1, có nghĩa là số ngẫu nhiên. Nếu bạn đã cấu hình tường lửa, nên đặt điều này.",
"connect_request": "Cho phép {client} kết nối?",
"connection_request_denied": "Kết nối bị từ chối. Người dùng đã từ chối quyền truy cập."
}

View File

@ -426,5 +426,9 @@
"download": "下载",
"file_not_found": "文件未找到",
"custom": "自定义",
"add_custom_url": "添加自定义 URL"
"add_custom_url": "添加自定义 URL",
"edit_port": "编辑端口",
"port_helper_msg": "默认值为-1表示随机数。如果您已配置防火墙建议设置此项。",
"connect_request": "允许 {client} 连接吗?",
"connection_request_denied": "连接被拒绝。用户拒绝访问。"
}

View File

@ -2704,6 +2704,30 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Add custom URL'**
String get add_custom_url;
/// No description provided for @edit_port.
///
/// In en, this message translates to:
/// **'Edit port'**
String get edit_port;
/// No description provided for @port_helper_msg.
///
/// 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<AppLocalizations> {

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsAr extends AppLocalizations {
@override
String get add_custom_url => 'إضافة URL مخصص';
@override
String get edit_port => 'تعديل المنفذ';
@override
String get port_helper_msg => 'القيمة الافتراضية هي -1 والتي تشير إلى رقم عشوائي. إذا كان لديك جدار ناري مُعد، يُوصى بتعيين هذا.';
@override
String connect_request(Object client) {
return 'السماح لـ $client بالاتصال؟';
}
@override
String get connection_request_denied => 'تم رفض الاتصال. المستخدم رفض الوصول.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsBn extends AppLocalizations {
@override
String get add_custom_url => 'কাস্টম URL যোগ করুন';
@override
String get edit_port => 'পোর্ট সম্পাদনা করুন';
@override
String get port_helper_msg => 'ডিফল্ট হল -1 যা এলোমেলো সংখ্যা নির্দেশ করে। যদি আপনার ফায়ারওয়াল কনফিগার করা থাকে, তবে এটি সেট করা সুপারিশ করা হয়।';
@override
String connect_request(Object client) {
return '$client কে সংযোগ করতে অনুমতি দেবেন?';
}
@override
String get connection_request_denied => 'সংযোগ অস্বীকৃত। ব্যবহারকারী প্রবেশাধিকার অস্বীকার করেছে।';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsCa extends AppLocalizations {
@override
String get add_custom_url => 'Afegir URL personalitzada';
@override
String get edit_port => 'Editar port';
@override
String get port_helper_msg => 'El valor per defecte és -1, que indica un número aleatori. Si teniu un tallafoc configurat, es recomana establir-ho.';
@override
String connect_request(Object client) {
return 'Permetre que $client es connecti?';
}
@override
String get connection_request_denied => 'Connexió denegada. L\'usuari ha denegat l\'accés.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsCs extends AppLocalizations {
@override
String get add_custom_url => 'Přidat vlastní URL';
@override
String get edit_port => 'Upravit port';
@override
String get port_helper_msg => 'Výchozí hodnota je -1, což znamená náhodné číslo. Pokud máte nakonfigurován firewall, doporučuje se to nastavit.';
@override
String connect_request(Object client) {
return 'Povolit $client připojení?';
}
@override
String get connection_request_denied => 'Připojení bylo zamítnuto. Uživatel odmítl přístup.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get add_custom_url => 'Benutzerdefinierte URL hinzufügen';
@override
String get edit_port => 'Port bearbeiten';
@override
String get port_helper_msg => 'Der Standardwert ist -1, was eine zufällige Zahl bedeutet. Wenn Sie eine Firewall konfiguriert haben, wird empfohlen, dies einzustellen.';
@override
String connect_request(Object client) {
return '$client die Verbindung erlauben?';
}
@override
String get connection_request_denied => 'Verbindung abgelehnt. Benutzer hat den Zugriff verweigert.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get add_custom_url => 'Add custom URL';
@override
String get edit_port => 'Edit port';
@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.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get add_custom_url => 'Agregar URL personalizada';
@override
String get edit_port => 'Editar puerto';
@override
String get port_helper_msg => 'El valor predeterminado es -1, lo que indica un número aleatorio. Si tienes un firewall configurado, se recomienda establecer esto.';
@override
String connect_request(Object client) {
return '¿Permitir que $client se conecte?';
}
@override
String get connection_request_denied => 'Conexión denegada. El usuario denegó el acceso.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsEu extends AppLocalizations {
@override
String get add_custom_url => 'Gehitu URL pertsonalizatua';
@override
String get edit_port => 'Editatu portua';
@override
String get port_helper_msg => 'Lehenetsitako balioa -1 da, zenbaki aleatorioa adierazten duena. Su firewall konfiguratu baduzu, gomendatzen da hau ezartzea.';
@override
String connect_request(Object client) {
return '$client konektatzea baimendu?';
}
@override
String get connection_request_denied => 'Konektatzea ukatu da. Erabiltzaileak sarbidea ukatu du.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsFa extends AppLocalizations {
@override
String get add_custom_url => 'اضافه کردن URL سفارشی';
@override
String get edit_port => 'ویرایش پورت';
@override
String get port_helper_msg => 'پیش‌فرض -1 است که نشان‌دهنده یک عدد تصادفی است. اگر فایروال شما پیکربندی شده است، توصیه می‌شود این را تنظیم کنید.';
@override
String connect_request(Object client) {
return 'آیا اجازه می‌دهید $client متصل شود؟';
}
@override
String get connection_request_denied => 'اتصال رد شد. کاربر دسترسی را رد کرد.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsFi extends AppLocalizations {
@override
String get add_custom_url => 'Lisää mukautettu URL';
@override
String get edit_port => 'Muokkaa porttia';
@override
String get port_helper_msg => 'Oletusarvo on -1, mikä tarkoittaa satunnaista numeroa. Jos sinulla on palomuuri määritetty, tämän asettamista suositellaan.';
@override
String connect_request(Object client) {
return 'Salli $client yhdistää?';
}
@override
String get connection_request_denied => 'Yhteys evätty. Käyttäjä eväsi pääsyn.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get add_custom_url => 'Ajouter une URL personnalisée';
@override
String get edit_port => 'Modifier le port';
@override
String get port_helper_msg => 'La valeur par défaut est -1, ce qui indique un nombre aléatoire. Si vous avez configuré un pare-feu, il est recommandé de le définir.';
@override
String connect_request(Object client) {
return 'Autoriser $client à se connecter ?';
}
@override
String get connection_request_denied => 'Connection denied. User denied access.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsHi extends AppLocalizations {
@override
String get add_custom_url => 'कस्टम URL जोड़ें';
@override
String get edit_port => 'पोर्ट संपादित करें';
@override
String get port_helper_msg => 'डिफ़ॉल्ट -1 है जो यादृच्छिक संख्या को दर्शाता है। यदि आपने फ़ायरवॉल कॉन्फ़िगर किया है, तो इसे सेट करना अनुशंसित है।';
@override
String connect_request(Object client) {
return '$client को कनेक्ट करने की अनुमति दें?';
}
@override
String get connection_request_denied => 'कनेक्शन अस्वीकृत। उपयोगकर्ता ने पहुंच अस्वीकृत कर दी।';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get add_custom_url => 'Add custom URL';
@override
String get edit_port => 'Edit port';
@override
String get port_helper_msg => 'Default adalah -1 yang menunjukkan angka acak. Jika Anda telah mengonfigurasi firewall, disarankan untuk mengatur ini.';
@override
String connect_request(Object client) {
return 'Izinkan $client untuk terhubung?';
}
@override
String get connection_request_denied => 'Koneksi ditolak. Pengguna menolak akses.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsIt extends AppLocalizations {
@override
String get add_custom_url => 'Aggiungi URL personalizzato';
@override
String get edit_port => 'Modifica porta';
@override
String get port_helper_msg => 'Il valore predefinito è -1, che indica un numero casuale. Se hai configurato un firewall, si consiglia di impostarlo.';
@override
String connect_request(Object client) {
return 'Consentire a $client di connettersi?';
}
@override
String get connection_request_denied => 'Connessione negata. L\'utente ha negato l\'accesso.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsJa extends AppLocalizations {
@override
String get add_custom_url => 'カスタムURLを追加';
@override
String get edit_port => 'ポートを編集';
@override
String get port_helper_msg => 'デフォルトは-1で、ランダムな番号を示します。ファイアウォールを設定している場合は、これを設定することをお勧めします。';
@override
String connect_request(Object client) {
return '$clientの接続を許可しますか';
}
@override
String get connection_request_denied => '接続が拒否されました。ユーザーがアクセスを拒否しました。';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsKa extends AppLocalizations {
@override
String get add_custom_url => 'დამატება პერსონალური URL';
@override
String get edit_port => 'პორტის რედაქტირება';
@override
String get port_helper_msg => 'ნაგულისხმევი არის -1, რაც შემთხვევითი ნომრის მითითებას ნიშნავს. თუ لديك firewall настроен, рекомендуется установить это.';
@override
String connect_request(Object client) {
return '$client-ის დაკავშირების ნებართვა?';
}
@override
String get connection_request_denied => 'კავშირი უარყოფილია. მომხმარებელმა უარყო წვდომა.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get add_custom_url => '사용자 정의 URL 추가';
@override
String get edit_port => '포트 편집';
@override
String get port_helper_msg => '기본값은 -1로 무작위 숫자를 나타냅니다. 방화벽이 구성된 경우 이를 설정하는 것이 좋습니다.';
@override
String connect_request(Object client) {
return '$client의 연결을 허용하시겠습니까?';
}
@override
String get connection_request_denied => '연결이 거부되었습니다. 사용자가 액세스를 거부했습니다.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsNe extends AppLocalizations {
@override
String get add_custom_url => 'कस्टम URL जोड़ें';
@override
String get edit_port => 'पोर्ट सम्पादन गर्नुहोस्';
@override
String get port_helper_msg => 'डिफ़ॉल्ट -1 हो जुन यादृच्छिक संख्या जनाउँछ। यदि तपाईंले फायरवाल कन्फिगर गर्नुभएको छ भने, यसलाई सेट गर्न सिफारिस गरिन्छ।';
@override
String connect_request(Object client) {
return '$client लाई जडान गर्न अनुमति दिनुहोस्?';
}
@override
String get connection_request_denied => 'जडान अस्वीकृत। प्रयोगकर्ताले पहुँच अस्वीकृत गर्यो।';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get add_custom_url => 'Voeg aangepaste URL toe';
@override
String get edit_port => 'Poort bewerken';
@override
String get port_helper_msg => 'Standaard is -1, wat een willekeurig nummer aangeeft. Als je een firewall hebt geconfigureerd, wordt aanbevolen dit in te stellen.';
@override
String connect_request(Object client) {
return 'Toestaan dat $client verbinding maakt?';
}
@override
String get connection_request_denied => 'Verbinding geweigerd. Gebruiker heeft toegang geweigerd.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsPl extends AppLocalizations {
@override
String get add_custom_url => 'Dodaj niestandardowy URL';
@override
String get edit_port => 'Edytuj port';
@override
String get port_helper_msg => 'Domyślna wartość to -1, co oznacza losową liczbę. Jeśli masz skonfigurowany zaporę, zaleca się jej ustawienie.';
@override
String connect_request(Object client) {
return 'Zezwolić $client na połączenie?';
}
@override
String get connection_request_denied => 'Połączenie odrzucone. Użytkownik odmówił dostępu.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get add_custom_url => 'Adicionar URL personalizada';
@override
String get edit_port => 'Editar porta';
@override
String get port_helper_msg => 'O padrão é -1, que indica um número aleatório. Se você tiver um firewall configurado, é recomendável definir isso.';
@override
String connect_request(Object client) {
return 'Permitir que $client se conecte?';
}
@override
String get connection_request_denied => 'Conexão negada. O usuário negou o acesso .';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get add_custom_url => 'Добавить пользовательский URL';
@override
String get edit_port => 'Редактировать порт';
@override
String get port_helper_msg => 'По умолчанию -1, что означает случайное число. Если у вас настроен брандмауэр, рекомендуется установить это.';
@override
String connect_request(Object client) {
return 'Разрешить $client подключение?';
}
@override
String get connection_request_denied => 'Подключение отклонено. Пользователь отказал в доступе.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsTa extends AppLocalizations {
@override
String get add_custom_url => 'தனிப்பயன் URL ஐச் சேர்க்கவும்';
@override
String get edit_port => 'போர்டு திருத்தவும்';
@override
String get port_helper_msg => 'இயல்புநிலை -1 ஆகும், இது சீரற்ற எண்ணை குறிக்கிறது. நீங்கள் தீயணைப்பு அமைக்கப்பட்டிருந்தால், இதை அமைப்பது பரிந்துரைக்கப்படுகிறது.';
@override
String connect_request(Object client) {
return '$client க்கு இணைக்க அனுமதிக்கவா?';
}
@override
String get connection_request_denied => 'இணைப்பு மறுக்கப்பட்டது. பயனர் அணுகலை மறுத்தார்.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsTh extends AppLocalizations {
@override
String get add_custom_url => 'เพิ่ม URL แบบกำหนดเอง';
@override
String get edit_port => 'แก้ไขพอร์ต';
@override
String get port_helper_msg => 'ค่าเริ่มต้นคือ -1 ซึ่งหมายถึงหมายเลขสุ่ม หากคุณได้กำหนดค่าไฟร์วอลล์แล้ว แนะนำให้ตั้งค่านี้';
@override
String connect_request(Object client) {
return 'อนุญาตให้ $client เชื่อมต่อหรือไม่?';
}
@override
String get connection_request_denied => 'การเชื่อมต่อล้มเหลว ผู้ใช้ปฏิเสธการเข้าถึง';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsTl extends AppLocalizations {
@override
String get add_custom_url => 'Magdagdag ng custom URL';
@override
String get edit_port => 'I-edit ang port';
@override
String get port_helper_msg => 'Ang default ay -1 na nagpapahiwatig ng random na numero. Kung na-configure mo ang firewall, inirerekomenda na itakda ito.';
@override
String connect_request(Object client) {
return 'Payagan ang $client na kumonekta?';
}
@override
String get connection_request_denied => 'Tanggihan ang koneksyon. Tinanggihan ng gumagamit ang pag-access.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsTr extends AppLocalizations {
@override
String get add_custom_url => 'Özel URL ekle';
@override
String get edit_port => 'Portu düzenle';
@override
String get port_helper_msg => 'Varsayılan -1\'dir, bu da rastgele bir sayıyı gösterir. Bir güvenlik duvarınız varsa, bunu ayarlamanız önerilir.';
@override
String connect_request(Object client) {
return '$client bağlantısına izin verilsin mi?';
}
@override
String get connection_request_denied => 'Bağlantı reddedildi. Kullanıcı erişimi reddetti.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsUk extends AppLocalizations {
@override
String get add_custom_url => 'Додати користувацький URL';
@override
String get edit_port => 'Редагувати порт';
@override
String get port_helper_msg => 'За замовчуванням -1, що означає випадкове число. Якщо у вас налаштований брандмауер, рекомендується це налаштувати.';
@override
String connect_request(Object client) {
return 'Дозволити $client підключення?';
}
@override
String get connection_request_denied => 'Підключення відхилено. Користувач відмовив у доступі.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsVi extends AppLocalizations {
@override
String get add_custom_url => 'Thêm URL tùy chỉnh';
@override
String get edit_port => 'Chỉnh sửa cổng';
@override
String get port_helper_msg => 'Mặc định là -1, có nghĩa là số ngẫu nhiên. Nếu bạn đã cấu hình tường lửa, nên đặt điều này.';
@override
String connect_request(Object client) {
return 'Cho phép $client kết nối?';
}
@override
String get connection_request_denied => 'Kết nối bị từ chối. Người dùng đã từ chối quyền truy cập.';
}

View File

@ -1371,4 +1371,18 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get add_custom_url => '添加自定义 URL';
@override
String get edit_port => '编辑端口';
@override
String get port_helper_msg => '默认值为-1表示随机数。如果您已配置防火墙建议设置此项。';
@override
String connect_request(Object client) {
return '允许 $client 连接吗?';
}
@override
String get connection_request_denied => '连接被拒绝。用户拒绝访问。';
}

View File

@ -62,7 +62,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
@override
int get schemaVersion => 5;
int get schemaVersion => 6;
@override
MigrationStrategy get migration {
@ -114,6 +114,13 @@ class AppDatabase extends _$AppDatabase {
"SET $columnName = 'Orange:0xFFf97315' WHERE $columnName = 'Blue:0xFF2196F3'",
);
},
from5To6: (m, schema) async {
// Add new column to preferences table
await m.addColumn(
schema.preferencesTable,
schema.preferencesTable.connectPort,
);
},
),
);
}

View File

@ -823,6 +823,14 @@ class $PreferencesTableTable extends PreferencesTable
defaultConstraints: GeneratedColumn.constraintIsAlways(
'CHECK ("enable_connect" IN (0, 1))'),
defaultValue: const Constant(false));
static const VerificationMeta _connectPortMeta =
const VerificationMeta('connectPort');
@override
late final GeneratedColumn<int> connectPort = GeneratedColumn<int>(
'connect_port', aliasedName, false,
type: DriftSqlType.int,
requiredDuringInsert: false,
defaultValue: const Constant(-1));
static const VerificationMeta _cacheMusicMeta =
const VerificationMeta('cacheMusic');
@override
@ -862,6 +870,7 @@ class $PreferencesTableTable extends PreferencesTable
discordPresence,
endlessPlayback,
enableConnect,
connectPort,
cacheMusic
];
@override
@ -971,6 +980,12 @@ class $PreferencesTableTable extends PreferencesTable
enableConnect.isAcceptableOrUnknown(
data['enable_connect']!, _enableConnectMeta));
}
if (data.containsKey('connect_port')) {
context.handle(
_connectPortMeta,
connectPort.isAcceptableOrUnknown(
data['connect_port']!, _connectPortMeta));
}
if (data.containsKey('cache_music')) {
context.handle(
_cacheMusicMeta,
@ -1054,6 +1069,8 @@ class $PreferencesTableTable extends PreferencesTable
.read(DriftSqlType.bool, data['${effectivePrefix}endless_playback'])!,
enableConnect: attachedDatabase.typeMapping
.read(DriftSqlType.bool, data['${effectivePrefix}enable_connect'])!,
connectPort: attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}connect_port'])!,
cacheMusic: attachedDatabase.typeMapping
.read(DriftSqlType.bool, data['${effectivePrefix}cache_music'])!,
);
@ -1126,6 +1143,7 @@ class PreferencesTableData extends DataClass
final bool discordPresence;
final bool endlessPlayback;
final bool enableConnect;
final int connectPort;
final bool cacheMusic;
const PreferencesTableData(
{required this.id,
@ -1155,6 +1173,7 @@ class PreferencesTableData extends DataClass
required this.discordPresence,
required this.endlessPlayback,
required this.enableConnect,
required this.connectPort,
required this.cacheMusic});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
@ -1230,6 +1249,7 @@ class PreferencesTableData extends DataClass
map['discord_presence'] = Variable<bool>(discordPresence);
map['endless_playback'] = Variable<bool>(endlessPlayback);
map['enable_connect'] = Variable<bool>(enableConnect);
map['connect_port'] = Variable<int>(connectPort);
map['cache_music'] = Variable<bool>(cacheMusic);
return map;
}
@ -1263,6 +1283,7 @@ class PreferencesTableData extends DataClass
discordPresence: Value(discordPresence),
endlessPlayback: Value(endlessPlayback),
enableConnect: Value(enableConnect),
connectPort: Value(connectPort),
cacheMusic: Value(cacheMusic),
);
}
@ -1310,6 +1331,7 @@ class PreferencesTableData extends DataClass
discordPresence: serializer.fromJson<bool>(json['discordPresence']),
endlessPlayback: serializer.fromJson<bool>(json['endlessPlayback']),
enableConnect: serializer.fromJson<bool>(json['enableConnect']),
connectPort: serializer.fromJson<int>(json['connectPort']),
cacheMusic: serializer.fromJson<bool>(json['cacheMusic']),
);
}
@ -1358,6 +1380,7 @@ class PreferencesTableData extends DataClass
'discordPresence': serializer.toJson<bool>(discordPresence),
'endlessPlayback': serializer.toJson<bool>(endlessPlayback),
'enableConnect': serializer.toJson<bool>(enableConnect),
'connectPort': serializer.toJson<int>(connectPort),
'cacheMusic': serializer.toJson<bool>(cacheMusic),
};
}
@ -1390,6 +1413,7 @@ class PreferencesTableData extends DataClass
bool? discordPresence,
bool? endlessPlayback,
bool? enableConnect,
int? connectPort,
bool? cacheMusic}) =>
PreferencesTableData(
id: id ?? this.id,
@ -1419,6 +1443,7 @@ class PreferencesTableData extends DataClass
discordPresence: discordPresence ?? this.discordPresence,
endlessPlayback: endlessPlayback ?? this.endlessPlayback,
enableConnect: enableConnect ?? this.enableConnect,
connectPort: connectPort ?? this.connectPort,
cacheMusic: cacheMusic ?? this.cacheMusic,
);
PreferencesTableData copyWithCompanion(PreferencesTableCompanion data) {
@ -1492,6 +1517,8 @@ class PreferencesTableData extends DataClass
enableConnect: data.enableConnect.present
? data.enableConnect.value
: this.enableConnect,
connectPort:
data.connectPort.present ? data.connectPort.value : this.connectPort,
cacheMusic:
data.cacheMusic.present ? data.cacheMusic.value : this.cacheMusic,
);
@ -1527,6 +1554,7 @@ class PreferencesTableData extends DataClass
..write('discordPresence: $discordPresence, ')
..write('endlessPlayback: $endlessPlayback, ')
..write('enableConnect: $enableConnect, ')
..write('connectPort: $connectPort, ')
..write('cacheMusic: $cacheMusic')
..write(')'))
.toString();
@ -1561,6 +1589,7 @@ class PreferencesTableData extends DataClass
discordPresence,
endlessPlayback,
enableConnect,
connectPort,
cacheMusic
]);
@override
@ -1594,6 +1623,7 @@ class PreferencesTableData extends DataClass
other.discordPresence == this.discordPresence &&
other.endlessPlayback == this.endlessPlayback &&
other.enableConnect == this.enableConnect &&
other.connectPort == this.connectPort &&
other.cacheMusic == this.cacheMusic);
}
@ -1625,6 +1655,7 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
final Value<bool> discordPresence;
final Value<bool> endlessPlayback;
final Value<bool> enableConnect;
final Value<int> connectPort;
final Value<bool> cacheMusic;
const PreferencesTableCompanion({
this.id = const Value.absent(),
@ -1654,6 +1685,7 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
this.discordPresence = const Value.absent(),
this.endlessPlayback = const Value.absent(),
this.enableConnect = const Value.absent(),
this.connectPort = const Value.absent(),
this.cacheMusic = const Value.absent(),
});
PreferencesTableCompanion.insert({
@ -1684,6 +1716,7 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
this.discordPresence = const Value.absent(),
this.endlessPlayback = const Value.absent(),
this.enableConnect = const Value.absent(),
this.connectPort = const Value.absent(),
this.cacheMusic = const Value.absent(),
});
static Insertable<PreferencesTableData> custom({
@ -1714,6 +1747,7 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
Expression<bool>? discordPresence,
Expression<bool>? endlessPlayback,
Expression<bool>? enableConnect,
Expression<int>? connectPort,
Expression<bool>? cacheMusic,
}) {
return RawValuesInsertable({
@ -1748,6 +1782,7 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
if (discordPresence != null) 'discord_presence': discordPresence,
if (endlessPlayback != null) 'endless_playback': endlessPlayback,
if (enableConnect != null) 'enable_connect': enableConnect,
if (connectPort != null) 'connect_port': connectPort,
if (cacheMusic != null) 'cache_music': cacheMusic,
});
}
@ -1780,6 +1815,7 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
Value<bool>? discordPresence,
Value<bool>? endlessPlayback,
Value<bool>? enableConnect,
Value<int>? connectPort,
Value<bool>? cacheMusic}) {
return PreferencesTableCompanion(
id: id ?? this.id,
@ -1809,6 +1845,7 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
discordPresence: discordPresence ?? this.discordPresence,
endlessPlayback: endlessPlayback ?? this.endlessPlayback,
enableConnect: enableConnect ?? this.enableConnect,
connectPort: connectPort ?? this.connectPort,
cacheMusic: cacheMusic ?? this.cacheMusic,
);
}
@ -1918,6 +1955,9 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
if (enableConnect.present) {
map['enable_connect'] = Variable<bool>(enableConnect.value);
}
if (connectPort.present) {
map['connect_port'] = Variable<int>(connectPort.value);
}
if (cacheMusic.present) {
map['cache_music'] = Variable<bool>(cacheMusic.value);
}
@ -1954,6 +1994,7 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
..write('discordPresence: $discordPresence, ')
..write('endlessPlayback: $endlessPlayback, ')
..write('enableConnect: $enableConnect, ')
..write('connectPort: $connectPort, ')
..write('cacheMusic: $cacheMusic')
..write(')'))
.toString();
@ -4626,6 +4667,7 @@ typedef $$PreferencesTableTableCreateCompanionBuilder
Value<bool> discordPresence,
Value<bool> endlessPlayback,
Value<bool> enableConnect,
Value<int> connectPort,
Value<bool> cacheMusic,
});
typedef $$PreferencesTableTableUpdateCompanionBuilder
@ -4657,6 +4699,7 @@ typedef $$PreferencesTableTableUpdateCompanionBuilder
Value<bool> discordPresence,
Value<bool> endlessPlayback,
Value<bool> enableConnect,
Value<int> connectPort,
Value<bool> cacheMusic,
});
@ -4786,6 +4829,9 @@ class $$PreferencesTableTableFilterComposer
ColumnFilters<bool> get enableConnect => $composableBuilder(
column: $table.enableConnect, builder: (column) => ColumnFilters(column));
ColumnFilters<int> get connectPort => $composableBuilder(
column: $table.connectPort, builder: (column) => ColumnFilters(column));
ColumnFilters<bool> get cacheMusic => $composableBuilder(
column: $table.cacheMusic, builder: (column) => ColumnFilters(column));
}
@ -4899,6 +4945,9 @@ class $$PreferencesTableTableOrderingComposer
column: $table.enableConnect,
builder: (column) => ColumnOrderings(column));
ColumnOrderings<int> get connectPort => $composableBuilder(
column: $table.connectPort, builder: (column) => ColumnOrderings(column));
ColumnOrderings<bool> get cacheMusic => $composableBuilder(
column: $table.cacheMusic, builder: (column) => ColumnOrderings(column));
}
@ -5003,6 +5052,9 @@ class $$PreferencesTableTableAnnotationComposer
GeneratedColumn<bool> get enableConnect => $composableBuilder(
column: $table.enableConnect, builder: (column) => column);
GeneratedColumn<int> get connectPort => $composableBuilder(
column: $table.connectPort, builder: (column) => column);
GeneratedColumn<bool> get cacheMusic => $composableBuilder(
column: $table.cacheMusic, builder: (column) => column);
}
@ -5063,6 +5115,7 @@ class $$PreferencesTableTableTableManager extends RootTableManager<
Value<bool> discordPresence = const Value.absent(),
Value<bool> endlessPlayback = const Value.absent(),
Value<bool> enableConnect = const Value.absent(),
Value<int> connectPort = const Value.absent(),
Value<bool> cacheMusic = const Value.absent(),
}) =>
PreferencesTableCompanion(
@ -5093,6 +5146,7 @@ class $$PreferencesTableTableTableManager extends RootTableManager<
discordPresence: discordPresence,
endlessPlayback: endlessPlayback,
enableConnect: enableConnect,
connectPort: connectPort,
cacheMusic: cacheMusic,
),
createCompanionCallback: ({
@ -5124,6 +5178,7 @@ class $$PreferencesTableTableTableManager extends RootTableManager<
Value<bool> discordPresence = const Value.absent(),
Value<bool> endlessPlayback = const Value.absent(),
Value<bool> enableConnect = const Value.absent(),
Value<int> connectPort = const Value.absent(),
Value<bool> cacheMusic = const Value.absent(),
}) =>
PreferencesTableCompanion.insert(
@ -5154,6 +5209,7 @@ class $$PreferencesTableTableTableManager extends RootTableManager<
discordPresence: discordPresence,
endlessPlayback: endlessPlayback,
enableConnect: enableConnect,
connectPort: connectPort,
cacheMusic: cacheMusic,
),
withReferenceMapper: (p0) => p0

View File

@ -1,11 +1,11 @@
// dart format width=80
import 'package:drift/internal/versioned_schema.dart' as i0;
import 'package:drift/drift.dart' as i1;
import 'package:drift/drift.dart';
import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import
import 'package:flutter/material.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/models/database/database.dart';
import 'package:spotube/services/sourced_track/enums.dart'; // ignore_for_file: type=lint,unused_import
import 'package:spotube/services/sourced_track/enums.dart';
// GENERATED BY drift_dev, DO NOT MODIFY.
final class Schema2 extends i0.VersionedSchema {
@ -1409,11 +1409,295 @@ i1.GeneratedColumn<String> _column_55(String aliasedName) =>
i1.GeneratedColumn<String>('accent_color_scheme', aliasedName, false,
type: i1.DriftSqlType.string,
defaultValue: const Constant("Orange:0xFFf97315"));
final class Schema6 extends i0.VersionedSchema {
Schema6({required super.database}) : super(version: 6);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
authenticationTable,
blacklistTable,
preferencesTable,
scrobblerTable,
skipSegmentTable,
sourceMatchTable,
audioPlayerStateTable,
playlistTable,
playlistMediaTable,
historyTable,
lyricsTable,
uniqueBlacklist,
uniqTrackMatch,
];
late final Shape0 authenticationTable = Shape0(
source: i0.VersionedTable(
entityName: 'authentication_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_2,
_column_3,
],
attachedDatabase: database,
),
alias: null);
late final Shape1 blacklistTable = Shape1(
source: i0.VersionedTable(
entityName: 'blacklist_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_4,
_column_5,
_column_6,
],
attachedDatabase: database,
),
alias: null);
late final Shape13 preferencesTable = Shape13(
source: i0.VersionedTable(
entityName: 'preferences_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_7,
_column_8,
_column_9,
_column_10,
_column_11,
_column_12,
_column_13,
_column_14,
_column_15,
_column_55,
_column_17,
_column_18,
_column_19,
_column_20,
_column_21,
_column_22,
_column_23,
_column_24,
_column_25,
_column_26,
_column_54,
_column_27,
_column_28,
_column_29,
_column_30,
_column_31,
_column_56,
_column_53,
],
attachedDatabase: database,
),
alias: null);
late final Shape3 scrobblerTable = Shape3(
source: i0.VersionedTable(
entityName: 'scrobbler_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_32,
_column_33,
_column_34,
],
attachedDatabase: database,
),
alias: null);
late final Shape4 skipSegmentTable = Shape4(
source: i0.VersionedTable(
entityName: 'skip_segment_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_35,
_column_36,
_column_37,
_column_32,
],
attachedDatabase: database,
),
alias: null);
late final Shape5 sourceMatchTable = Shape5(
source: i0.VersionedTable(
entityName: 'source_match_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_37,
_column_38,
_column_39,
_column_32,
],
attachedDatabase: database,
),
alias: null);
late final Shape6 audioPlayerStateTable = Shape6(
source: i0.VersionedTable(
entityName: 'audio_player_state_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_40,
_column_41,
_column_42,
_column_43,
],
attachedDatabase: database,
),
alias: null);
late final Shape7 playlistTable = Shape7(
source: i0.VersionedTable(
entityName: 'playlist_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_44,
_column_45,
],
attachedDatabase: database,
),
alias: null);
late final Shape8 playlistMediaTable = Shape8(
source: i0.VersionedTable(
entityName: 'playlist_media_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_46,
_column_47,
_column_48,
_column_49,
],
attachedDatabase: database,
),
alias: null);
late final Shape9 historyTable = Shape9(
source: i0.VersionedTable(
entityName: 'history_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_32,
_column_50,
_column_51,
_column_52,
],
attachedDatabase: database,
),
alias: null);
late final Shape10 lyricsTable = Shape10(
source: i0.VersionedTable(
entityName: 'lyrics_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_37,
_column_52,
],
attachedDatabase: database,
),
alias: null);
final i1.Index uniqueBlacklist = i1.Index('unique_blacklist',
'CREATE UNIQUE INDEX unique_blacklist ON blacklist_table (element_type, element_id)');
final i1.Index uniqTrackMatch = i1.Index('uniq_track_match',
'CREATE UNIQUE INDEX uniq_track_match ON source_match_table (track_id, source_id, source_type)');
}
class Shape13 extends i0.VersionedTable {
Shape13({required super.source, required super.alias}) : super.aliased();
i1.GeneratedColumn<int> get id =>
columnsByName['id']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<String> get audioQuality =>
columnsByName['audio_quality']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<bool> get albumColorSync =>
columnsByName['album_color_sync']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<bool> get amoledDarkTheme =>
columnsByName['amoled_dark_theme']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<bool> get checkUpdate =>
columnsByName['check_update']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<bool> get normalizeAudio =>
columnsByName['normalize_audio']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<bool> get showSystemTrayIcon =>
columnsByName['show_system_tray_icon']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<bool> get systemTitleBar =>
columnsByName['system_title_bar']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<bool> get skipNonMusic =>
columnsByName['skip_non_music']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<String> get closeBehavior =>
columnsByName['close_behavior']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get accentColorScheme =>
columnsByName['accent_color_scheme']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get layoutMode =>
columnsByName['layout_mode']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get locale =>
columnsByName['locale']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get market =>
columnsByName['market']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get searchMode =>
columnsByName['search_mode']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get downloadLocation =>
columnsByName['download_location']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get localLibraryLocation =>
columnsByName['local_library_location']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get pipedInstance =>
columnsByName['piped_instance']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get invidiousInstance =>
columnsByName['invidious_instance']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get themeMode =>
columnsByName['theme_mode']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get audioSource =>
columnsByName['audio_source']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get youtubeClientEngine =>
columnsByName['youtube_client_engine']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get streamMusicCodec =>
columnsByName['stream_music_codec']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<String> get downloadMusicCodec =>
columnsByName['download_music_codec']! as i1.GeneratedColumn<String>;
i1.GeneratedColumn<bool> get discordPresence =>
columnsByName['discord_presence']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<bool> get endlessPlayback =>
columnsByName['endless_playback']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<bool> get enableConnect =>
columnsByName['enable_connect']! as i1.GeneratedColumn<bool>;
i1.GeneratedColumn<int> get connectPort =>
columnsByName['connect_port']! as i1.GeneratedColumn<int>;
i1.GeneratedColumn<bool> get cacheMusic =>
columnsByName['cache_music']! as i1.GeneratedColumn<bool>;
}
i1.GeneratedColumn<int> _column_56(String aliasedName) =>
i1.GeneratedColumn<int>('connect_port', aliasedName, false,
type: i1.DriftSqlType.int, defaultValue: const Constant(-1));
i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6,
}) {
return (currentVersion, database) async {
switch (currentVersion) {
@ -1437,6 +1721,11 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema);
await from4To5(migrator, schema);
return 5;
case 5:
final schema = Schema6(database: database);
final migrator = i1.Migrator(database, schema);
await from5To6(migrator, schema);
return 6;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
@ -1448,6 +1737,7 @@ i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
required Future<void> Function(i1.Migrator m, Schema6 schema) from5To6,
}) =>
i0.VersionedSchema.stepByStepHelper(
step: migrationSteps(
@ -1455,4 +1745,5 @@ i1.OnUpgrade stepByStep({
from2To3: from2To3,
from3To4: from3To4,
from4To5: from4To5,
from5To6: from5To6,
));

View File

@ -115,6 +115,7 @@ class PreferencesTable extends Table {
boolean().withDefault(const Constant(true))();
BoolColumn get enableConnect =>
boolean().withDefault(const Constant(false))();
IntColumn get connectPort => integer().withDefault(const Constant(-1))();
BoolColumn get cacheMusic => boolean().withDefault(const Constant(true))();
// Default values as PreferencesTableData
@ -148,6 +149,7 @@ class PreferencesTable extends Table {
endlessPlayback: true,
enableConnect: false,
cacheMusic: true,
connectPort: -1,
);
}
}

View File

@ -0,0 +1,97 @@
import 'package:flutter/services.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/components/form/text_form_field.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
class SettingsPlaybackEditConnectPortDialog extends HookConsumerWidget {
const SettingsPlaybackEditConnectPortDialog({super.key});
@override
Widget build(BuildContext context, ref) {
final connectPort = ref.watch(
userPreferencesProvider.select((s) => s.connectPort),
);
final controller = useShadcnTextEditingController(
text: connectPort.toString(),
);
final formKey = useMemoized(() => GlobalKey<FormBuilderState>(), []);
return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: Alert(
title: Text(context.l10n.edit_port).h4(),
content: FormBuilder(
key: formKey,
child: Column(
children: [
const Gap(10),
TextFormBuilderField(
name: "port",
controller: controller,
placeholder: const Text("3000"),
validator: FormBuilderValidators.integer(radix: 10),
keyboardType: TextInputType.number,
inputFormatters: [
// 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),
Text(context.l10n.port_helper_msg).small.muted,
const Gap(20),
Row(
children: [
Expanded(
child: Button.secondary(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(context.l10n.cancel),
),
),
const Gap(10),
Expanded(
child: Button.primary(
onPressed: () {
if (!formKey.currentState!.saveAndValidate()) {
return;
}
final port = int.parse(controller.text);
ref
.read(userPreferencesProvider.notifier)
.setConnectPort(port);
Navigator.of(context).pop();
},
child: Text(context.l10n.save),
),
),
],
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,75 @@
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/components/form/text_form_field.dart';
import 'package:spotube/extensions/context.dart';
import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart';
class SettingsPlaybackEditInstanceUrlDialog extends HookConsumerWidget {
final String title;
final String? initialValue;
final ValueChanged<String> onSave;
const SettingsPlaybackEditInstanceUrlDialog({
super.key,
required this.title,
required this.onSave,
this.initialValue,
});
@override
Widget build(BuildContext context, ref) {
final controller = useShadcnTextEditingController(
text: initialValue,
);
final formKey = useMemoized(() => GlobalKey<FormBuilderState>(), []);
return Alert(
title: Text(title).h4(),
content: FormBuilder(
key: formKey,
child: Column(
children: [
const Gap(10),
TextFormBuilderField(
name: "url",
controller: controller,
placeholder: Text(title),
validator: FormBuilderValidators.url(),
),
const Gap(10),
Row(
children: [
Expanded(
child: Button.secondary(
onPressed: () {
Navigator.of(context).pop();
},
child: Text(context.l10n.cancel),
),
),
const Gap(10),
Expanded(
child: Button.primary(
onPressed: () {
if (!formKey.currentState!.saveAndValidate()) {
return;
}
onSave(
controller.text,
);
Navigator.of(context).pop();
},
child: Text(context.l10n.save),
),
),
],
)
],
),
),
);
}
}

View File

@ -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

View File

@ -4,9 +4,6 @@ import 'package:auto_route/auto_route.dart';
import 'package:collection/collection.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart' show ListTile;
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -14,9 +11,9 @@ import 'package:piped_client/piped_client.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart' hide Consumer;
import 'package:spotube/collections/routes.gr.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/form/text_form_field.dart';
import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart';
import 'package:spotube/models/database/database.dart';
import 'package:spotube/modules/settings/playback/edit_connect_port_dialog.dart';
import 'package:spotube/modules/settings/playback/edit_instance_url_dialog.dart';
import 'package:spotube/modules/settings/section_card_with_heading.dart';
import 'package:spotube/components/adaptive/adaptive_select_tile.dart';
import 'package:spotube/extensions/context.dart';
@ -114,67 +111,12 @@ class SettingsPlaybackSection extends HookConsumerWidget {
showDialog(
context: context,
barrierColor: Colors.black.withValues(alpha: 0.5),
builder: (context) => HookBuilder(
builder: (context) {
final controller =
useShadcnTextEditingController(
text: preferences.pipedInstance,
);
final formKey = useMemoized(
() => GlobalKey<FormBuilderState>(), []);
return Alert(
title:
Text(context.l10n.piped_instance).h4(),
content: FormBuilder(
key: formKey,
child: Column(
children: [
const Gap(10),
TextFormBuilderField(
name: "url",
controller: controller,
placeholder: Text(
context.l10n.piped_instance),
validator:
FormBuilderValidators.url(),
),
const Gap(10),
Row(
children: [
Expanded(
child: Button.secondary(
onPressed: () {
Navigator.of(context).pop();
},
child:
Text(context.l10n.cancel),
),
),
const Gap(10),
Expanded(
child: Button.primary(
onPressed: () {
if (!formKey.currentState!
.saveAndValidate()) {
return;
}
preferencesNotifier
.setPipedInstance(
controller.text,
);
Navigator.of(context).pop();
},
child:
Text(context.l10n.save),
),
),
],
)
],
),
),
);
builder: (context) =>
SettingsPlaybackEditInstanceUrlDialog(
title: context.l10n.piped_instance,
initialValue: preferences.pipedInstance,
onSave: (value) {
preferencesNotifier.setPipedInstance(value);
},
),
);
@ -269,67 +211,13 @@ class SettingsPlaybackSection extends HookConsumerWidget {
showDialog(
context: context,
barrierColor: Colors.black.withValues(alpha: 0.5),
builder: (context) => HookBuilder(
builder: (context) {
final controller =
useShadcnTextEditingController(
text: preferences.invidiousInstance,
);
final formKey = useMemoized(
() => GlobalKey<FormBuilderState>(), []);
return Alert(
title: Text(context.l10n.invidious_instance)
.h4(),
content: FormBuilder(
key: formKey,
child: Column(
children: [
const Gap(10),
TextFormBuilderField(
name: "url",
controller: controller,
placeholder: Text(context
.l10n.invidious_instance),
validator:
FormBuilderValidators.url(),
),
const Gap(10),
Row(
children: [
Expanded(
child: Button.secondary(
onPressed: () {
Navigator.of(context).pop();
},
child:
Text(context.l10n.cancel),
),
),
const Gap(10),
Expanded(
child: Button.primary(
onPressed: () {
if (!formKey.currentState!
.saveAndValidate()) {
return;
}
builder: (context) =>
SettingsPlaybackEditInstanceUrlDialog(
title: context.l10n.invidious_instance,
initialValue: preferences.invidiousInstance,
onSave: (value) {
preferencesNotifier
.setInvidiousInstance(
controller.text,
);
Navigator.of(context).pop();
},
child:
Text(context.l10n.save),
),
),
],
)
],
),
),
);
.setInvidiousInstance(value);
},
),
);
@ -561,10 +449,33 @@ class SettingsPlaybackSection extends HookConsumerWidget {
title: Text(context.l10n.enable_connect),
subtitle: Text(context.l10n.enable_connect_description),
leading: const Icon(SpotubeIcons.connect),
trailing: Switch(
trailing: Row(
mainAxisSize: MainAxisSize.min,
spacing: 10,
children: [
Tooltip(
tooltip: TooltipContainer(
child: Text(context.l10n.edit_port),
).call,
child: IconButton.outline(
icon: const Icon(SpotubeIcons.edit),
size: ButtonSize.small,
onPressed: () {
showDialog(
context: context,
barrierColor: Colors.black.withValues(alpha: 0.5),
builder: (context) =>
const SettingsPlaybackEditConnectPortDialog(),
);
},
),
),
Switch(
value: preferences.enableConnect,
onChanged: preferencesNotifier.setEnableConnect,
),
],
),
),
],
);

View File

@ -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<double>(
(ref) => 1.0,
);
class ConnectNotifier extends AsyncNotifier<WebSocketChannel?> {
typedef ConnectState = ({WebSocketChannel channel, Stream stream});
class ConnectNotifier extends AsyncNotifier<ConnectState?> {
@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<WebSocketChannel?> {
'✅ 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<WebSocketChannel?> {
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<WebSocketChannel?> {
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<WebSocketChannel?> {
Future<void> 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<WebSocketChannel?> {
}
}
final connectProvider =
AsyncNotifierProvider<ConnectNotifier, WebSocketChannel?>(
final connectProvider = AsyncNotifierProvider<ConnectNotifier, ConnectState?>(
() => ConnectNotifier(),
);

View File

@ -15,6 +15,7 @@ import 'package:spotube/models/local_track.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
// ignore: depend_on_referenced_packages
import 'package:flutter_rust_bridge/flutter_rust_bridge.dart' show FrbException;
import 'package:spotube/utils/service_utils.dart';
const supportedAudioTypes = [
"audio/webm",
@ -90,12 +91,15 @@ final localTracksProvider =
try {
final metadata = await MetadataGod.readMetadata(file: file.path);
final imageFile = File(join(
final imageFile = File(
join(
(await getTemporaryDirectory()).path,
"spotube",
basenameWithoutExtension(file.path) +
ServiceUtils.sanitizeFilename(
basenameWithoutExtension(file.path)) +
imgMimeToExt[metadata.picture?.mimeType ?? "image/jpeg"]!,
));
),
);
if (!await imageFile.exists() && metadata.picture != null) {
await imageFile.create(recursive: true);
await imageFile.writeAsBytes(

View File

@ -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<String> get connectClientStream =>
_connectClientStreamController.stream;
final List<String> _allowedConnections = [];
FutureOr<Response> 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<bool>(
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),

View File

@ -54,8 +54,10 @@ class ServerPlaybackRoutes {
final trackCacheFile = File(
join(
await UserPreferencesNotifier.getMusicCacheDir(),
ServiceUtils.sanitizeFilename(
'${track.name} - ${track.artists?.asString()} (${track.sourceInfo.id}).${track.codec.name}',
),
),
);
final trackPartialCacheFile = File("${trackCacheFile.path}.part");

View File

@ -5,30 +5,51 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shelf/shelf_io.dart';
import 'package:spotube/provider/server/pipeline.dart';
import 'package:spotube/provider/server/router.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
import 'package:spotube/services/audio_player/audio_player.dart';
import 'package:spotube/services/logger/logger.dart';
final serverProvider = FutureProvider(
(ref) async {
final enabledRemoteConnect = ref.watch(
userPreferencesProvider.select((value) => value.enableConnect),
);
final connectPort = ref.watch(
userPreferencesProvider.select((value) => value.connectPort),
);
final pipeline = ref.watch(pipelineProvider);
final router = ref.watch(serverRouterProvider);
final port = Random().nextInt(17500) + 5000;
// When connect port is -1, we need to generate a random port
// but we shouldn't reset it if it's already been set (caused by a state change)
if (connectPort == -1) {
if (SpotubeMedia.serverPort == 0) {
final port = Random().nextInt(17500) + 5000;
SpotubeMedia.serverPort = port;
}
} else {
SpotubeMedia.serverPort = connectPort;
}
final server = await serve(
pipeline.addHandler(router.call),
InternetAddress.anyIPv4,
port,
enabledRemoteConnect
? InternetAddress.anyIPv4
: InternetAddress.loopbackIPv4,
SpotubeMedia.serverPort,
);
AppLogger.log
.t('Playback server at http://${server.address.host}:${server.port}');
AppLogger.log.t(
'Playback server at http://${server.address.host}:${server.port}',
);
ref.onDispose(() {
server.close();
});
return (server: server, port: port);
return (
server: server,
port: SpotubeMedia.serverPort,
);
},
);

View File

@ -240,6 +240,14 @@ class UserPreferencesNotifier extends Notifier<PreferencesTableData> {
setData(PreferencesTableCompanion(enableConnect: Value(enable)));
}
void setConnectPort(int port) {
assert(
port >= -1 && port <= 65535,
"Port must be between -1 and 65535, got $port",
);
setData(PreferencesTableCompanion(connectPort: Value(port)));
}
void setCacheMusic(bool cache) {
setData(PreferencesTableCompanion(cacheMusic: Value(cache)));
}

View File

@ -434,4 +434,37 @@ abstract class ServiceUtils {
return "Mozilla/5.0 (Linux; Android ${randomNumber(8, 13)}) AppleWebKit/${randomNumber(530, 537)}.${randomNumber(30, 36)} (KHTML, like Gecko) Chrome/${randomNumber(101, 116)}.0.${randomNumber(3000, 6000)}.${randomNumber(60, 125)} Mobile Safari/${randomNumber(530, 537)}.${randomNumber(30, 36)}";
}
}
static String sanitizeFilename(String input, {String replacement = ''}) {
final result = input
// illegalRe
.replaceAll(
RegExp(r'[\/\?<>\\:\*\|"]'),
replacement,
)
// controlRe
.replaceAll(
RegExp(
r'[\x00-\x1f\x80-\x9f]',
),
replacement,
)
// reservedRe
.replaceFirst(
RegExp(r'^\.+$'),
replacement,
)
// windowsReservedRe
.replaceFirst(
RegExp(
r'^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$',
caseSensitive: false,
),
replacement,
)
// windowsTrailingRe
.replaceFirst(RegExp(r'[\. ]+$'), replacement);
return result.length > 255 ? result.substring(0, 255) : result;
}
}

View File

@ -5,6 +5,7 @@ import 'package:drift/drift.dart';
import 'package:drift/internal/migrations.dart';
import 'schema_v3.dart' as v3;
import 'schema_v5.dart' as v5;
import 'schema_v6.dart' as v6;
import 'schema_v1.dart' as v1;
import 'schema_v2.dart' as v2;
import 'schema_v4.dart' as v4;
@ -17,6 +18,8 @@ class GeneratedHelper implements SchemaInstantiationHelper {
return v3.DatabaseAtV3(db);
case 5:
return v5.DatabaseAtV5(db);
case 6:
return v6.DatabaseAtV6(db);
case 1:
return v1.DatabaseAtV1(db);
case 2:
@ -28,5 +31,5 @@ class GeneratedHelper implements SchemaInstantiationHelper {
}
}
static const versions = const [1, 2, 3, 4, 5];
static const versions = const [1, 2, 3, 4, 5, 6];
}

File diff suppressed because it is too large Load Diff

View File

@ -1 +1,5 @@
{}
{
"fr": [
"connection_request_denied"
]
}