feat: replace QR remote pairing with encrypted LAN discovery
Replace session ID + PIN auth with HKDF-derived home secret and per-session AES-256-GCM encrypted channel. Auto-start remote server on host devices, auto-discover hosts via authenticated UDP broadcast.
This commit is contained in:
+26
-36
@@ -238,7 +238,9 @@
|
|||||||
"player": "Player",
|
"player": "Player",
|
||||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||||
"seekAndTiming": "Seek & Timing",
|
"seekAndTiming": "Seek & Timing",
|
||||||
"behavior": "Behavior"
|
"behavior": "Behavior",
|
||||||
|
"companionRemoteServer": "Companion Remote Server",
|
||||||
|
"companionRemoteServerDescription": "Tillad mobilenheder på dit netværk at styre denne app"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"hint": "Søg film, serier, musik...",
|
"hint": "Søg film, serier, musik...",
|
||||||
@@ -778,48 +780,36 @@
|
|||||||
"controlDesktop": "Styr en desktopenhed",
|
"controlDesktop": "Styr en desktopenhed",
|
||||||
"connectedTo": "Forbundet til ${name}",
|
"connectedTo": "Forbundet til ${name}",
|
||||||
"session": {
|
"session": {
|
||||||
"creatingSession": "Opretter fjernsession...",
|
"startingServer": "Starter fjernserver...",
|
||||||
"failedToCreate": "Kunne ikke oprette fjernsession:",
|
"failedToCreate": "Kunne ikke starte fjernserver:",
|
||||||
"noSession": "Ingen session tilgængelig",
|
"hostAddress": "Værtsadresse",
|
||||||
"scanQrCode": "Scan QR-kode",
|
|
||||||
"orEnterManually": "Eller indtast manuelt",
|
|
||||||
"hostAddress": "Værtadresse",
|
|
||||||
"sessionId": "Sessions-ID",
|
|
||||||
"pin": "PIN",
|
|
||||||
"connected": "Forbundet",
|
"connected": "Forbundet",
|
||||||
"waitingForConnection": "Venter på forbindelse...",
|
"serverRunning": "Fjernserver aktiv",
|
||||||
|
"serverStopped": "Fjernserver stoppet",
|
||||||
|
"serverRunningDescription": "Mobilenheder på dit netværk kan finde og oprette forbindelse til denne app",
|
||||||
|
"serverStoppedDescription": "Start serveren for at tillade mobilenheder at oprette forbindelse",
|
||||||
"usePhoneToControl": "Brug din mobilenhed til at styre denne app",
|
"usePhoneToControl": "Brug din mobilenhed til at styre denne app",
|
||||||
"copiedToClipboard": "${label} kopieret til udklipsholder",
|
"startServer": "Start server",
|
||||||
"copyToClipboard": "Kopiér til udklipsholder",
|
"stopServer": "Stop server",
|
||||||
"newSession": "Ny session",
|
|
||||||
"minimize": "Minimér"
|
"minimize": "Minimér"
|
||||||
},
|
},
|
||||||
"pairing": {
|
"pairing": {
|
||||||
"scan": "Scan",
|
"pairWithDesktop": "Opret forbindelse til computer",
|
||||||
"manual": "Manuel",
|
"discoveryDescription": "Enheder på dit netværk, der kører Plezy med den samme Plex-konto, vises automatisk",
|
||||||
"pairWithDesktop": "Par med desktop",
|
|
||||||
"enterSessionDetails": "Indtast sessionsdetaljerne vist på din desktopenhed",
|
|
||||||
"hostAddressHint": "192.168.1.100:48632",
|
"hostAddressHint": "192.168.1.100:48632",
|
||||||
"sessionIdHint": "Indtast 8-tegns sessions-ID",
|
"connecting": "Opretter forbindelse...",
|
||||||
"pinHint": "Indtast 6-cifret PIN",
|
"searchingForDevices": "Søger efter enheder...",
|
||||||
"connecting": "Forbinder...",
|
"noDevicesFound": "Ingen enheder fundet på dit netværk",
|
||||||
"tips": "Tips",
|
"noDevicesHint": "Sørg for, at Plezy er åben på din computer, og at begge enheder er på det samme WiFi-netværk",
|
||||||
"tipDesktop": "Åbn Plezy på din desktop og aktivér fjernbetjening fra indstillinger eller menu",
|
"availableDevices": "Tilgængelige enheder",
|
||||||
"tipScan": "Brug fanen Scan til hurtigt at parre ved at scanne QR-koden på din desktop",
|
"manualConnection": "Manuel forbindelse",
|
||||||
"tipWifi": "Sørg for, at begge enheder er på det samme WiFi-netværk",
|
"cryptoInitFailed": "Kunne ikke initialisere sikker forbindelse. Sørg for, at du er logget ind på en Plex-konto.",
|
||||||
"cameraPermissionRequired": "Kameratilladelse er påkrævet for at scanne QR-koder.\nGiv kameraadgang i enhedsindstillinger.",
|
"validationHostRequired": "Angiv venligst værtsadresse",
|
||||||
"cameraError": "Kunne ikke starte kamera: ${error}",
|
|
||||||
"scanInstruction": "Ret kameraet mod QR-koden vist på din desktop",
|
|
||||||
"invalidQrCode": "Ugyldigt QR-kodeformat",
|
|
||||||
"validationHostRequired": "Indtast værtadresse",
|
|
||||||
"validationHostFormat": "Format skal være IP:port (f.eks. 192.168.1.100:48632)",
|
"validationHostFormat": "Format skal være IP:port (f.eks. 192.168.1.100:48632)",
|
||||||
"validationSessionIdRequired": "Indtast et sessions-ID",
|
"connectionTimedOut": "Forbindelsen udløb. Sørg for, at begge enheder er på det samme netværk.",
|
||||||
"validationSessionIdLength": "Sessions-ID skal være 8 tegn",
|
"sessionNotFound": "Kunne ikke finde enheden. Sørg for, at Plezy kører på værten.",
|
||||||
"validationPinRequired": "Indtast en PIN",
|
"authFailed": "Godkendelse mislykkedes. Sørg for, at begge enheder bruger den samme Plex-konto.",
|
||||||
"validationPinLength": "PIN skal være 6 cifre",
|
"failedToConnect": "Kunne ikke oprette forbindelse: ${error}"
|
||||||
"connectionTimedOut": "Forbindelsen fik timeout. Tjek sessions-ID og PIN.",
|
|
||||||
"sessionNotFound": "Kunne ikke finde sessionen. Tjek dine oplysninger.",
|
|
||||||
"failedToConnect": "Kunne ikke forbinde: ${error}"
|
|
||||||
},
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
"disconnectConfirm": "Vil du afbryde fra fjernsessionen?",
|
"disconnectConfirm": "Vil du afbryde fra fjernsessionen?",
|
||||||
|
|||||||
+23
-33
@@ -238,7 +238,9 @@
|
|||||||
"player": "Player",
|
"player": "Player",
|
||||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||||
"seekAndTiming": "Seek & Timing",
|
"seekAndTiming": "Seek & Timing",
|
||||||
"behavior": "Behavior"
|
"behavior": "Behavior",
|
||||||
|
"companionRemoteServer": "Companion Remote Server",
|
||||||
|
"companionRemoteServerDescription": "Mobilgeräten in deinem Netzwerk erlauben, diese App zu steuern"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"hint": "Filme, Serien, Musik suchen...",
|
"hint": "Filme, Serien, Musik suchen...",
|
||||||
@@ -778,47 +780,35 @@
|
|||||||
"controlDesktop": "Ein Desktop-Gerät steuern",
|
"controlDesktop": "Ein Desktop-Gerät steuern",
|
||||||
"connectedTo": "Verbunden mit ${name}",
|
"connectedTo": "Verbunden mit ${name}",
|
||||||
"session": {
|
"session": {
|
||||||
"creatingSession": "Fernsteuerungssitzung wird erstellt...",
|
"startingServer": "Remote-Server wird gestartet...",
|
||||||
"failedToCreate": "Fernsteuerungssitzung konnte nicht erstellt werden:",
|
"failedToCreate": "Remote-Server konnte nicht gestartet werden:",
|
||||||
"noSession": "Keine Sitzung verfügbar",
|
|
||||||
"scanQrCode": "QR-Code scannen",
|
|
||||||
"orEnterManually": "Oder manuell eingeben",
|
|
||||||
"hostAddress": "Host-Adresse",
|
"hostAddress": "Host-Adresse",
|
||||||
"sessionId": "Sitzungs-ID",
|
|
||||||
"pin": "PIN",
|
|
||||||
"connected": "Verbunden",
|
"connected": "Verbunden",
|
||||||
"waitingForConnection": "Warte auf Verbindung...",
|
"serverRunning": "Remote-Server aktiv",
|
||||||
|
"serverStopped": "Remote-Server gestoppt",
|
||||||
|
"serverRunningDescription": "Mobilgeräte in deinem Netzwerk können diese App finden und sich verbinden",
|
||||||
|
"serverStoppedDescription": "Starte den Server, um Mobilgeräten die Verbindung zu ermöglichen",
|
||||||
"usePhoneToControl": "Verwende dein Mobilgerät, um diese App zu steuern",
|
"usePhoneToControl": "Verwende dein Mobilgerät, um diese App zu steuern",
|
||||||
"copiedToClipboard": "${label} in Zwischenablage kopiert",
|
"startServer": "Server starten",
|
||||||
"copyToClipboard": "In Zwischenablage kopieren",
|
"stopServer": "Server stoppen",
|
||||||
"newSession": "Neue Sitzung",
|
|
||||||
"minimize": "Minimieren"
|
"minimize": "Minimieren"
|
||||||
},
|
},
|
||||||
"pairing": {
|
"pairing": {
|
||||||
"scan": "Scannen",
|
"pairWithDesktop": "Mit Desktop verbinden",
|
||||||
"manual": "Manuell",
|
"discoveryDescription": "Geräte in deinem Netzwerk, die Plezy mit demselben Plex-Konto nutzen, erscheinen automatisch",
|
||||||
"pairWithDesktop": "Mit Desktop koppeln",
|
|
||||||
"enterSessionDetails": "Gib die Sitzungsdetails ein, die auf deinem Desktop-Gerät angezeigt werden",
|
|
||||||
"hostAddressHint": "192.168.1.100:48632",
|
"hostAddressHint": "192.168.1.100:48632",
|
||||||
"sessionIdHint": "8-stellige Sitzungs-ID eingeben",
|
"connecting": "Verbinden...",
|
||||||
"pinHint": "6-stellige PIN eingeben",
|
"searchingForDevices": "Suche nach Geräten...",
|
||||||
"connecting": "Verbinde...",
|
"noDevicesFound": "Keine Geräte in deinem Netzwerk gefunden",
|
||||||
"tips": "Tipps",
|
"noDevicesHint": "Stelle sicher, dass Plezy auf deinem Desktop geöffnet ist und beide Geräte im selben WLAN sind",
|
||||||
"tipDesktop": "Öffne Plezy auf deinem Desktop und aktiviere Companion Remote in den Einstellungen oder im Menü",
|
"availableDevices": "Verfügbare Geräte",
|
||||||
"tipScan": "Verwende den Scannen-Tab, um dich schnell per QR-Code auf deinem Desktop zu koppeln",
|
"manualConnection": "Manuelle Verbindung",
|
||||||
"tipWifi": "Stelle sicher, dass beide Geräte im selben WiFi-Netzwerk sind",
|
"cryptoInitFailed": "Sichere Verbindung konnte nicht initialisiert werden. Stelle sicher, dass du bei einem Plex-Konto angemeldet bist.",
|
||||||
"cameraPermissionRequired": "Kameraberechtigung wird zum Scannen von QR-Codes benötigt.\nBitte erteile den Kamerazugriff in den Geräteeinstellungen.",
|
|
||||||
"cameraError": "Kamera konnte nicht gestartet werden: ${error}",
|
|
||||||
"scanInstruction": "Richte deine Kamera auf den QR-Code auf deinem Desktop",
|
|
||||||
"invalidQrCode": "Ungültiges QR-Code-Format",
|
|
||||||
"validationHostRequired": "Bitte Host-Adresse eingeben",
|
"validationHostRequired": "Bitte Host-Adresse eingeben",
|
||||||
"validationHostFormat": "Format muss IP:Port sein (z.B. 192.168.1.100:48632)",
|
"validationHostFormat": "Format muss IP:Port sein (z.B. 192.168.1.100:48632)",
|
||||||
"validationSessionIdRequired": "Bitte Sitzungs-ID eingeben",
|
"connectionTimedOut": "Verbindung abgelaufen. Stelle sicher, dass beide Geräte im selben Netzwerk sind.",
|
||||||
"validationSessionIdLength": "Sitzungs-ID muss 8 Zeichen lang sein",
|
"sessionNotFound": "Gerät nicht gefunden. Stelle sicher, dass Plezy auf dem Host läuft.",
|
||||||
"validationPinRequired": "Bitte PIN eingeben",
|
"authFailed": "Authentifizierung fehlgeschlagen. Stelle sicher, dass beide Geräte dasselbe Plex-Konto verwenden.",
|
||||||
"validationPinLength": "PIN muss 6 Ziffern haben",
|
|
||||||
"connectionTimedOut": "Zeitüberschreitung. Bitte Sitzungs-ID und PIN überprüfen.",
|
|
||||||
"sessionNotFound": "Sitzung nicht gefunden. Bitte Zugangsdaten überprüfen.",
|
|
||||||
"failedToConnect": "Verbindung fehlgeschlagen: ${error}"
|
"failedToConnect": "Verbindung fehlgeschlagen: ${error}"
|
||||||
},
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
|
|||||||
+21
-31
@@ -209,6 +209,8 @@
|
|||||||
"maxVolumePercent": "${percent}%",
|
"maxVolumePercent": "${percent}%",
|
||||||
"discordRichPresence": "Discord Rich Presence",
|
"discordRichPresence": "Discord Rich Presence",
|
||||||
"discordRichPresenceDescription": "Show what you're watching on Discord",
|
"discordRichPresenceDescription": "Show what you're watching on Discord",
|
||||||
|
"companionRemoteServer": "Companion Remote Server",
|
||||||
|
"companionRemoteServerDescription": "Allow mobile devices on your network to control this app",
|
||||||
"autoPip": "Auto Picture-in-Picture",
|
"autoPip": "Auto Picture-in-Picture",
|
||||||
"autoPipDescription": "Automatically enter picture-in-picture when leaving the app during playback",
|
"autoPipDescription": "Automatically enter picture-in-picture when leaving the app during playback",
|
||||||
"matchContentFrameRate": "Match Content Frame Rate",
|
"matchContentFrameRate": "Match Content Frame Rate",
|
||||||
@@ -778,47 +780,35 @@
|
|||||||
"controlDesktop": "Control a desktop device",
|
"controlDesktop": "Control a desktop device",
|
||||||
"connectedTo": "Connected to ${name}",
|
"connectedTo": "Connected to ${name}",
|
||||||
"session": {
|
"session": {
|
||||||
"creatingSession": "Creating remote session...",
|
"startingServer": "Starting remote server...",
|
||||||
"failedToCreate": "Failed to create remote session:",
|
"failedToCreate": "Failed to start remote server:",
|
||||||
"noSession": "No session available",
|
|
||||||
"scanQrCode": "Scan QR Code",
|
|
||||||
"orEnterManually": "Or enter manually",
|
|
||||||
"hostAddress": "Host Address",
|
"hostAddress": "Host Address",
|
||||||
"sessionId": "Session ID",
|
|
||||||
"pin": "PIN",
|
|
||||||
"connected": "Connected",
|
"connected": "Connected",
|
||||||
"waitingForConnection": "Waiting for connection...",
|
"serverRunning": "Remote server active",
|
||||||
|
"serverStopped": "Remote server stopped",
|
||||||
|
"serverRunningDescription": "Mobile devices on your network can discover and connect to this app",
|
||||||
|
"serverStoppedDescription": "Start the server to allow mobile devices to connect",
|
||||||
"usePhoneToControl": "Use your mobile device to control this app",
|
"usePhoneToControl": "Use your mobile device to control this app",
|
||||||
"copiedToClipboard": "${label} copied to clipboard",
|
"startServer": "Start Server",
|
||||||
"copyToClipboard": "Copy to clipboard",
|
"stopServer": "Stop Server",
|
||||||
"newSession": "New Session",
|
|
||||||
"minimize": "Minimize"
|
"minimize": "Minimize"
|
||||||
},
|
},
|
||||||
"pairing": {
|
"pairing": {
|
||||||
"scan": "Scan",
|
"pairWithDesktop": "Connect to Desktop",
|
||||||
"manual": "Manual",
|
"discoveryDescription": "Devices on your network running Plezy with the same Plex account will appear automatically",
|
||||||
"pairWithDesktop": "Pair with Desktop",
|
|
||||||
"enterSessionDetails": "Enter the session details shown on your desktop device",
|
|
||||||
"hostAddressHint": "192.168.1.100:48632",
|
"hostAddressHint": "192.168.1.100:48632",
|
||||||
"sessionIdHint": "Enter 8-character session ID",
|
|
||||||
"pinHint": "Enter 6-digit PIN",
|
|
||||||
"connecting": "Connecting...",
|
"connecting": "Connecting...",
|
||||||
"tips": "Tips",
|
"searchingForDevices": "Looking for devices...",
|
||||||
"tipDesktop": "Open Plezy on your desktop and enable Companion Remote from settings or menu",
|
"noDevicesFound": "No devices found on your network",
|
||||||
"tipScan": "Use the Scan tab to quickly pair by scanning the QR code on your desktop",
|
"noDevicesHint": "Make sure Plezy is open on your desktop and both devices are on the same WiFi network",
|
||||||
"tipWifi": "Make sure both devices are on the same WiFi network",
|
"availableDevices": "Available Devices",
|
||||||
"cameraPermissionRequired": "Camera permission is required to scan QR codes.\nPlease grant camera access in your device settings.",
|
"manualConnection": "Manual Connection",
|
||||||
"cameraError": "Could not start camera: ${error}",
|
"cryptoInitFailed": "Could not initialize secure connection. Make sure you are signed in to a Plex account.",
|
||||||
"scanInstruction": "Point your camera at the QR code shown on your desktop",
|
|
||||||
"invalidQrCode": "Invalid QR code format",
|
|
||||||
"validationHostRequired": "Please enter host address",
|
"validationHostRequired": "Please enter host address",
|
||||||
"validationHostFormat": "Format must be IP:port (e.g., 192.168.1.100:48632)",
|
"validationHostFormat": "Format must be IP:port (e.g., 192.168.1.100:48632)",
|
||||||
"validationSessionIdRequired": "Please enter a session ID",
|
"connectionTimedOut": "Connection timed out. Make sure both devices are on the same network.",
|
||||||
"validationSessionIdLength": "Session ID must be 8 characters",
|
"sessionNotFound": "Could not find the device. Make sure Plezy is running on the host.",
|
||||||
"validationPinRequired": "Please enter a PIN",
|
"authFailed": "Authentication failed. Make sure both devices are on the same Plex account.",
|
||||||
"validationPinLength": "PIN must be 6 digits",
|
|
||||||
"connectionTimedOut": "Connection timed out. Please check the session ID and PIN.",
|
|
||||||
"sessionNotFound": "Could not find the session. Please check your credentials.",
|
|
||||||
"failedToConnect": "Failed to connect: ${error}"
|
"failedToConnect": "Failed to connect: ${error}"
|
||||||
},
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
|
|||||||
+25
-35
@@ -238,7 +238,9 @@
|
|||||||
"player": "Player",
|
"player": "Player",
|
||||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||||
"seekAndTiming": "Seek & Timing",
|
"seekAndTiming": "Seek & Timing",
|
||||||
"behavior": "Behavior"
|
"behavior": "Behavior",
|
||||||
|
"companionRemoteServer": "Servidor de control remoto",
|
||||||
|
"companionRemoteServerDescription": "Permitir que dispositivos móviles en tu red controlen esta aplicación"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"hint": "Buscar películas, series, música...",
|
"hint": "Buscar películas, series, música...",
|
||||||
@@ -778,47 +780,35 @@
|
|||||||
"controlDesktop": "Controlar un dispositivo de escritorio",
|
"controlDesktop": "Controlar un dispositivo de escritorio",
|
||||||
"connectedTo": "Conectado a ${name}",
|
"connectedTo": "Conectado a ${name}",
|
||||||
"session": {
|
"session": {
|
||||||
"creatingSession": "Creando sesión remota...",
|
"startingServer": "Iniciando servidor remoto...",
|
||||||
"failedToCreate": "Error al crear la sesión remota:",
|
"failedToCreate": "Error al iniciar el servidor remoto:",
|
||||||
"noSession": "No hay sesión disponible",
|
|
||||||
"scanQrCode": "Escanear código QR",
|
|
||||||
"orEnterManually": "O introducir manualmente",
|
|
||||||
"hostAddress": "Dirección del host",
|
"hostAddress": "Dirección del host",
|
||||||
"sessionId": "ID de sesión",
|
|
||||||
"pin": "PIN",
|
|
||||||
"connected": "Conectado",
|
"connected": "Conectado",
|
||||||
"waitingForConnection": "Esperando conexión...",
|
"serverRunning": "Servidor remoto activo",
|
||||||
"usePhoneToControl": "Usa tu dispositivo móvil para controlar esta app",
|
"serverStopped": "Servidor remoto detenido",
|
||||||
"copiedToClipboard": "${label} copiado al portapapeles",
|
"serverRunningDescription": "Los dispositivos móviles en tu red pueden descubrir y conectarse a esta aplicación",
|
||||||
"copyToClipboard": "Copiar al portapapeles",
|
"serverStoppedDescription": "Inicia el servidor para permitir que los dispositivos móviles se conecten",
|
||||||
"newSession": "Nueva sesión",
|
"usePhoneToControl": "Usa tu dispositivo móvil para controlar esta aplicación",
|
||||||
|
"startServer": "Iniciar servidor",
|
||||||
|
"stopServer": "Detener servidor",
|
||||||
"minimize": "Minimizar"
|
"minimize": "Minimizar"
|
||||||
},
|
},
|
||||||
"pairing": {
|
"pairing": {
|
||||||
"scan": "Escanear",
|
"pairWithDesktop": "Conectar al escritorio",
|
||||||
"manual": "Manual",
|
"discoveryDescription": "Los dispositivos en tu red que ejecutan Plezy con la misma cuenta de Plex aparecerán automáticamente",
|
||||||
"pairWithDesktop": "Emparejar con escritorio",
|
|
||||||
"enterSessionDetails": "Introduce los datos de la sesión que aparecen en tu dispositivo de escritorio",
|
|
||||||
"hostAddressHint": "192.168.1.100:48632",
|
"hostAddressHint": "192.168.1.100:48632",
|
||||||
"sessionIdHint": "Introduce ID de sesión de 8 caracteres",
|
|
||||||
"pinHint": "Introduce PIN de 6 dígitos",
|
|
||||||
"connecting": "Conectando...",
|
"connecting": "Conectando...",
|
||||||
"tips": "Consejos",
|
"searchingForDevices": "Buscando dispositivos...",
|
||||||
"tipDesktop": "Abre Plezy en tu escritorio y activa Companion Remote desde los ajustes o el menú",
|
"noDevicesFound": "No se encontraron dispositivos en tu red",
|
||||||
"tipScan": "Usa la pestaña Escanear para emparejar rápidamente escaneando el código QR de tu escritorio",
|
"noDevicesHint": "Asegúrate de que Plezy esté abierto en tu escritorio y que ambos dispositivos estén en la misma red WiFi",
|
||||||
"tipWifi": "Asegúrate de que ambos dispositivos estén en la misma red WiFi",
|
"availableDevices": "Dispositivos disponibles",
|
||||||
"cameraPermissionRequired": "Se necesita permiso de cámara para escanear códigos QR.\nPor favor, concede acceso a la cámara en los ajustes de tu dispositivo.",
|
"manualConnection": "Conexión manual",
|
||||||
"cameraError": "No se pudo iniciar la cámara: ${error}",
|
"cryptoInitFailed": "No se pudo inicializar la conexión segura. Asegúrate de haber iniciado sesión en una cuenta de Plex.",
|
||||||
"scanInstruction": "Apunta tu cámara al código QR que aparece en tu escritorio",
|
"validationHostRequired": "Ingresa la dirección del host",
|
||||||
"invalidQrCode": "Formato de código QR no válido",
|
"validationHostFormat": "El formato debe ser IP:puerto (ej. 192.168.1.100:48632)",
|
||||||
"validationHostRequired": "Por favor, introduce la dirección del host",
|
"connectionTimedOut": "Tiempo de conexión agotado. Asegúrate de que ambos dispositivos estén en la misma red.",
|
||||||
"validationHostFormat": "El formato debe ser IP:puerto (ej., 192.168.1.100:48632)",
|
"sessionNotFound": "No se encontró el dispositivo. Asegúrate de que Plezy esté ejecutándose en el host.",
|
||||||
"validationSessionIdRequired": "Por favor, introduce un ID de sesión",
|
"authFailed": "Autenticación fallida. Asegúrate de que ambos dispositivos usen la misma cuenta de Plex.",
|
||||||
"validationSessionIdLength": "El ID de sesión debe tener 8 caracteres",
|
|
||||||
"validationPinRequired": "Por favor, introduce un PIN",
|
|
||||||
"validationPinLength": "El PIN debe tener 6 dígitos",
|
|
||||||
"connectionTimedOut": "Tiempo de conexión agotado. Verifica el ID de sesión y el PIN.",
|
|
||||||
"sessionNotFound": "No se encontró la sesión. Verifica tus credenciales.",
|
|
||||||
"failedToConnect": "Error al conectar: ${error}"
|
"failedToConnect": "Error al conectar: ${error}"
|
||||||
},
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
|
|||||||
+24
-34
@@ -238,7 +238,9 @@
|
|||||||
"player": "Player",
|
"player": "Player",
|
||||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||||
"seekAndTiming": "Seek & Timing",
|
"seekAndTiming": "Seek & Timing",
|
||||||
"behavior": "Behavior"
|
"behavior": "Behavior",
|
||||||
|
"companionRemoteServer": "Serveur de télécommande",
|
||||||
|
"companionRemoteServerDescription": "Autoriser les appareils mobiles de votre réseau à contrôler cette application"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"hint": "Rechercher des films, des séries, de la musique...",
|
"hint": "Rechercher des films, des séries, de la musique...",
|
||||||
@@ -778,47 +780,35 @@
|
|||||||
"controlDesktop": "Contrôler un appareil de bureau",
|
"controlDesktop": "Contrôler un appareil de bureau",
|
||||||
"connectedTo": "Connecté à ${name}",
|
"connectedTo": "Connecté à ${name}",
|
||||||
"session": {
|
"session": {
|
||||||
"creatingSession": "Création de la session distante...",
|
"startingServer": "Démarrage du serveur distant...",
|
||||||
"failedToCreate": "Échec de la création de la session distante :",
|
"failedToCreate": "Échec du démarrage du serveur distant :",
|
||||||
"noSession": "Aucune session disponible",
|
|
||||||
"scanQrCode": "Scanner le QR Code",
|
|
||||||
"orEnterManually": "Ou saisir manuellement",
|
|
||||||
"hostAddress": "Adresse de l'hôte",
|
"hostAddress": "Adresse de l'hôte",
|
||||||
"sessionId": "ID de session",
|
|
||||||
"pin": "PIN",
|
|
||||||
"connected": "Connecté",
|
"connected": "Connecté",
|
||||||
"waitingForConnection": "En attente de connexion...",
|
"serverRunning": "Serveur distant actif",
|
||||||
|
"serverStopped": "Serveur distant arrêté",
|
||||||
|
"serverRunningDescription": "Les appareils mobiles de votre réseau peuvent découvrir et se connecter à cette application",
|
||||||
|
"serverStoppedDescription": "Démarrez le serveur pour permettre aux appareils mobiles de se connecter",
|
||||||
"usePhoneToControl": "Utilisez votre appareil mobile pour contrôler cette application",
|
"usePhoneToControl": "Utilisez votre appareil mobile pour contrôler cette application",
|
||||||
"copiedToClipboard": "${label} copié dans le presse-papiers",
|
"startServer": "Démarrer le serveur",
|
||||||
"copyToClipboard": "Copier dans le presse-papiers",
|
"stopServer": "Arrêter le serveur",
|
||||||
"newSession": "Nouvelle session",
|
|
||||||
"minimize": "Réduire"
|
"minimize": "Réduire"
|
||||||
},
|
},
|
||||||
"pairing": {
|
"pairing": {
|
||||||
"scan": "Scanner",
|
"pairWithDesktop": "Se connecter au bureau",
|
||||||
"manual": "Manuel",
|
"discoveryDescription": "Les appareils de votre réseau exécutant Plezy avec le même compte Plex apparaîtront automatiquement",
|
||||||
"pairWithDesktop": "Jumeler avec un bureau",
|
|
||||||
"enterSessionDetails": "Saisissez les détails de la session affichés sur votre appareil de bureau",
|
|
||||||
"hostAddressHint": "192.168.1.100:48632",
|
"hostAddressHint": "192.168.1.100:48632",
|
||||||
"sessionIdHint": "Saisissez l'ID de session à 8 caractères",
|
|
||||||
"pinHint": "Saisissez le PIN à 6 chiffres",
|
|
||||||
"connecting": "Connexion...",
|
"connecting": "Connexion...",
|
||||||
"tips": "Conseils",
|
"searchingForDevices": "Recherche d'appareils...",
|
||||||
"tipDesktop": "Ouvrez Plezy sur votre bureau et activez Companion Remote depuis les paramètres ou le menu",
|
"noDevicesFound": "Aucun appareil trouvé sur votre réseau",
|
||||||
"tipScan": "Utilisez l'onglet Scanner pour jumeler rapidement en scannant le QR code sur votre bureau",
|
"noDevicesHint": "Assurez-vous que Plezy est ouvert sur votre bureau et que les deux appareils sont sur le même réseau WiFi",
|
||||||
"tipWifi": "Assurez-vous que les deux appareils sont sur le même réseau WiFi",
|
"availableDevices": "Appareils disponibles",
|
||||||
"cameraPermissionRequired": "L'autorisation de la caméra est requise pour scanner les QR codes.\nVeuillez accorder l'accès à la caméra dans les paramètres de votre appareil.",
|
"manualConnection": "Connexion manuelle",
|
||||||
"cameraError": "Impossible de démarrer la caméra : ${error}",
|
"cryptoInitFailed": "Impossible d'initialiser la connexion sécurisée. Assurez-vous d'être connecté à un compte Plex.",
|
||||||
"scanInstruction": "Pointez votre caméra vers le QR code affiché sur votre bureau",
|
"validationHostRequired": "Veuillez entrer l'adresse de l'hôte",
|
||||||
"invalidQrCode": "Format de QR code invalide",
|
"validationHostFormat": "Le format doit être IP:port (ex. 192.168.1.100:48632)",
|
||||||
"validationHostRequired": "Veuillez saisir l'adresse de l'hôte",
|
"connectionTimedOut": "Délai de connexion dépassé. Assurez-vous que les deux appareils sont sur le même réseau.",
|
||||||
"validationHostFormat": "Le format doit être IP:port (ex : 192.168.1.100:48632)",
|
"sessionNotFound": "Appareil introuvable. Assurez-vous que Plezy est en cours d'exécution sur l'hôte.",
|
||||||
"validationSessionIdRequired": "Veuillez saisir un ID de session",
|
"authFailed": "Échec de l'authentification. Assurez-vous que les deux appareils utilisent le même compte Plex.",
|
||||||
"validationSessionIdLength": "L'ID de session doit contenir 8 caractères",
|
|
||||||
"validationPinRequired": "Veuillez saisir un PIN",
|
|
||||||
"validationPinLength": "Le PIN doit contenir 6 chiffres",
|
|
||||||
"connectionTimedOut": "Délai de connexion expiré. Veuillez vérifier l'ID de session et le PIN.",
|
|
||||||
"sessionNotFound": "Session introuvable. Veuillez vérifier vos identifiants.",
|
|
||||||
"failedToConnect": "Échec de la connexion : ${error}"
|
"failedToConnect": "Échec de la connexion : ${error}"
|
||||||
},
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
|
|||||||
+22
-32
@@ -238,7 +238,9 @@
|
|||||||
"player": "Player",
|
"player": "Player",
|
||||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||||
"seekAndTiming": "Seek & Timing",
|
"seekAndTiming": "Seek & Timing",
|
||||||
"behavior": "Behavior"
|
"behavior": "Behavior",
|
||||||
|
"companionRemoteServer": "Server telecomando",
|
||||||
|
"companionRemoteServerDescription": "Consenti ai dispositivi mobili nella tua rete di controllare questa app"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"hint": "Cerca film. spettacoli, musica...",
|
"hint": "Cerca film. spettacoli, musica...",
|
||||||
@@ -778,47 +780,35 @@
|
|||||||
"controlDesktop": "Controlla un dispositivo desktop",
|
"controlDesktop": "Controlla un dispositivo desktop",
|
||||||
"connectedTo": "Connesso a ${name}",
|
"connectedTo": "Connesso a ${name}",
|
||||||
"session": {
|
"session": {
|
||||||
"creatingSession": "Creazione sessione remota...",
|
"startingServer": "Avvio del server remoto...",
|
||||||
"failedToCreate": "Impossibile creare la sessione remota:",
|
"failedToCreate": "Impossibile avviare il server remoto:",
|
||||||
"noSession": "Nessuna sessione disponibile",
|
|
||||||
"scanQrCode": "Scansiona QR Code",
|
|
||||||
"orEnterManually": "Oppure inserisci manualmente",
|
|
||||||
"hostAddress": "Indirizzo host",
|
"hostAddress": "Indirizzo host",
|
||||||
"sessionId": "ID sessione",
|
|
||||||
"pin": "PIN",
|
|
||||||
"connected": "Connesso",
|
"connected": "Connesso",
|
||||||
"waitingForConnection": "In attesa di connessione...",
|
"serverRunning": "Server remoto attivo",
|
||||||
|
"serverStopped": "Server remoto arrestato",
|
||||||
|
"serverRunningDescription": "I dispositivi mobili nella tua rete possono scoprire e connettersi a questa app",
|
||||||
|
"serverStoppedDescription": "Avvia il server per consentire ai dispositivi mobili di connettersi",
|
||||||
"usePhoneToControl": "Usa il tuo dispositivo mobile per controllare questa app",
|
"usePhoneToControl": "Usa il tuo dispositivo mobile per controllare questa app",
|
||||||
"copiedToClipboard": "${label} copiato negli appunti",
|
"startServer": "Avvia server",
|
||||||
"copyToClipboard": "Copia negli appunti",
|
"stopServer": "Arresta server",
|
||||||
"newSession": "Nuova sessione",
|
|
||||||
"minimize": "Riduci"
|
"minimize": "Riduci"
|
||||||
},
|
},
|
||||||
"pairing": {
|
"pairing": {
|
||||||
"scan": "Scansiona",
|
"pairWithDesktop": "Connetti al desktop",
|
||||||
"manual": "Manuale",
|
"discoveryDescription": "I dispositivi nella tua rete che eseguono Plezy con lo stesso account Plex appariranno automaticamente",
|
||||||
"pairWithDesktop": "Associa con desktop",
|
|
||||||
"enterSessionDetails": "Inserisci i dettagli della sessione mostrati sul tuo dispositivo desktop",
|
|
||||||
"hostAddressHint": "192.168.1.100:48632",
|
"hostAddressHint": "192.168.1.100:48632",
|
||||||
"sessionIdHint": "Inserisci ID sessione di 8 caratteri",
|
|
||||||
"pinHint": "Inserisci PIN di 6 cifre",
|
|
||||||
"connecting": "Connessione...",
|
"connecting": "Connessione...",
|
||||||
"tips": "Suggerimenti",
|
"searchingForDevices": "Ricerca dispositivi...",
|
||||||
"tipDesktop": "Apri Plezy sul tuo desktop e abilita Companion Remote dalle impostazioni o dal menu",
|
"noDevicesFound": "Nessun dispositivo trovato nella tua rete",
|
||||||
"tipScan": "Usa la scheda Scansiona per associare rapidamente scansionando il QR code sul tuo desktop",
|
"noDevicesHint": "Assicurati che Plezy sia aperto sul tuo desktop e che entrambi i dispositivi siano sulla stessa rete WiFi",
|
||||||
"tipWifi": "Assicurati che entrambi i dispositivi siano sulla stessa rete WiFi",
|
"availableDevices": "Dispositivi disponibili",
|
||||||
"cameraPermissionRequired": "L'autorizzazione della fotocamera è necessaria per scansionare i QR code.\nConcedi l'accesso alla fotocamera nelle impostazioni del dispositivo.",
|
"manualConnection": "Connessione manuale",
|
||||||
"cameraError": "Impossibile avviare la fotocamera: ${error}",
|
"cryptoInitFailed": "Impossibile inizializzare la connessione sicura. Assicurati di aver effettuato l'accesso a un account Plex.",
|
||||||
"scanInstruction": "Punta la fotocamera verso il QR code mostrato sul tuo desktop",
|
|
||||||
"invalidQrCode": "Formato QR code non valido",
|
|
||||||
"validationHostRequired": "Inserisci l'indirizzo host",
|
"validationHostRequired": "Inserisci l'indirizzo host",
|
||||||
"validationHostFormat": "Il formato deve essere IP:porta (es. 192.168.1.100:48632)",
|
"validationHostFormat": "Il formato deve essere IP:porta (es. 192.168.1.100:48632)",
|
||||||
"validationSessionIdRequired": "Inserisci un ID sessione",
|
"connectionTimedOut": "Connessione scaduta. Assicurati che entrambi i dispositivi siano sulla stessa rete.",
|
||||||
"validationSessionIdLength": "L'ID sessione deve essere di 8 caratteri",
|
"sessionNotFound": "Dispositivo non trovato. Assicurati che Plezy sia in esecuzione sull'host.",
|
||||||
"validationPinRequired": "Inserisci un PIN",
|
"authFailed": "Autenticazione fallita. Assicurati che entrambi i dispositivi utilizzino lo stesso account Plex.",
|
||||||
"validationPinLength": "Il PIN deve essere di 6 cifre",
|
|
||||||
"connectionTimedOut": "Connessione scaduta. Verifica l'ID sessione e il PIN.",
|
|
||||||
"sessionNotFound": "Sessione non trovata. Verifica le tue credenziali.",
|
|
||||||
"failedToConnect": "Connessione fallita: ${error}"
|
"failedToConnect": "Connessione fallita: ${error}"
|
||||||
},
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
|
|||||||
+23
-33
@@ -238,7 +238,9 @@
|
|||||||
"player": "Player",
|
"player": "Player",
|
||||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||||
"seekAndTiming": "Seek & Timing",
|
"seekAndTiming": "Seek & Timing",
|
||||||
"behavior": "Behavior"
|
"behavior": "Behavior",
|
||||||
|
"companionRemoteServer": "コンパニオンリモートサーバー",
|
||||||
|
"companionRemoteServerDescription": "ネットワーク上のモバイルデバイスからこのアプリを操作できるようにする"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"hint": "映画、番組、音楽を検索...",
|
"hint": "映画、番組、音楽を検索...",
|
||||||
@@ -778,47 +780,35 @@
|
|||||||
"controlDesktop": "デスクトップデバイスを操作",
|
"controlDesktop": "デスクトップデバイスを操作",
|
||||||
"connectedTo": "${name}に接続中",
|
"connectedTo": "${name}に接続中",
|
||||||
"session": {
|
"session": {
|
||||||
"creatingSession": "リモートセッションを作成中...",
|
"startingServer": "リモートサーバーを起動中...",
|
||||||
"failedToCreate": "リモートセッションの作成に失敗しました:",
|
"failedToCreate": "リモートサーバーの起動に失敗しました:",
|
||||||
"noSession": "利用可能なセッションがありません",
|
|
||||||
"scanQrCode": "QRコードをスキャン",
|
|
||||||
"orEnterManually": "または手動で入力",
|
|
||||||
"hostAddress": "ホストアドレス",
|
"hostAddress": "ホストアドレス",
|
||||||
"sessionId": "セッションID",
|
|
||||||
"pin": "PIN",
|
|
||||||
"connected": "接続済み",
|
"connected": "接続済み",
|
||||||
"waitingForConnection": "接続を待機中...",
|
"serverRunning": "リモートサーバー稼働中",
|
||||||
"usePhoneToControl": "モバイルデバイスでこのアプリを操作",
|
"serverStopped": "リモートサーバー停止中",
|
||||||
"copiedToClipboard": "${label}をクリップボードにコピーしました",
|
"serverRunningDescription": "ネットワーク上のモバイルデバイスがこのアプリを検出して接続できます",
|
||||||
"copyToClipboard": "クリップボードにコピー",
|
"serverStoppedDescription": "モバイルデバイスの接続を許可するにはサーバーを起動してください",
|
||||||
"newSession": "新しいセッション",
|
"usePhoneToControl": "モバイルデバイスでこのアプリを操作できます",
|
||||||
|
"startServer": "サーバーを起動",
|
||||||
|
"stopServer": "サーバーを停止",
|
||||||
"minimize": "最小化"
|
"minimize": "最小化"
|
||||||
},
|
},
|
||||||
"pairing": {
|
"pairing": {
|
||||||
"scan": "スキャン",
|
"pairWithDesktop": "デスクトップに接続",
|
||||||
"manual": "手動",
|
"discoveryDescription": "同じPlexアカウントでPlezyを実行しているネットワーク上のデバイスが自動的に表示されます",
|
||||||
"pairWithDesktop": "デスクトップとペアリング",
|
|
||||||
"enterSessionDetails": "デスクトップデバイスに表示されたセッション情報を入力",
|
|
||||||
"hostAddressHint": "192.168.1.100:48632",
|
"hostAddressHint": "192.168.1.100:48632",
|
||||||
"sessionIdHint": "8文字のセッションIDを入力",
|
|
||||||
"pinHint": "6桁のPINを入力",
|
|
||||||
"connecting": "接続中...",
|
"connecting": "接続中...",
|
||||||
"tips": "ヒント",
|
"searchingForDevices": "デバイスを検索中...",
|
||||||
"tipDesktop": "デスクトップでPlezyを開き、設定またはメニューからコンパニオンリモートを有効にしてください",
|
"noDevicesFound": "ネットワーク上にデバイスが見つかりません",
|
||||||
"tipScan": "スキャンタブを使用して、デスクトップのQRコードをスキャンして素早くペアリング",
|
"noDevicesHint": "デスクトップでPlezyが開いていること、両方のデバイスが同じWiFiネットワーク上にあることを確認してください",
|
||||||
"tipWifi": "両方のデバイスが同じWiFiネットワークに接続されていることを確認",
|
"availableDevices": "利用可能なデバイス",
|
||||||
"cameraPermissionRequired": "QRコードをスキャンするにはカメラの権限が必要です。\nデバイスの設定でカメラへのアクセスを許可してください。",
|
"manualConnection": "手動接続",
|
||||||
"cameraError": "カメラを起動できませんでした: ${error}",
|
"cryptoInitFailed": "安全な接続を初期化できませんでした。Plexアカウントにサインインしていることを確認してください。",
|
||||||
"scanInstruction": "デスクトップに表示されたQRコードにカメラを向けてください",
|
|
||||||
"invalidQrCode": "無効なQRコード形式",
|
|
||||||
"validationHostRequired": "ホストアドレスを入力してください",
|
"validationHostRequired": "ホストアドレスを入力してください",
|
||||||
"validationHostFormat": "形式はIP:ポートである必要があります(例: 192.168.1.100:48632)",
|
"validationHostFormat": "形式はIP:ポートである必要があります(例: 192.168.1.100:48632)",
|
||||||
"validationSessionIdRequired": "セッションIDを入力してください",
|
"connectionTimedOut": "接続がタイムアウトしました。両方のデバイスが同じネットワーク上にあることを確認してください。",
|
||||||
"validationSessionIdLength": "セッションIDは8文字である必要があります",
|
"sessionNotFound": "デバイスが見つかりません。ホストでPlezyが実行されていることを確認してください。",
|
||||||
"validationPinRequired": "PINを入力してください",
|
"authFailed": "認証に失敗しました。両方のデバイスが同じPlexアカウントを使用していることを確認してください。",
|
||||||
"validationPinLength": "PINは6桁である必要があります",
|
|
||||||
"connectionTimedOut": "接続がタイムアウトしました。セッションIDとPINを確認してください。",
|
|
||||||
"sessionNotFound": "セッションが見つかりませんでした。認証情報を確認してください。",
|
|
||||||
"failedToConnect": "接続に失敗しました: ${error}"
|
"failedToConnect": "接続に失敗しました: ${error}"
|
||||||
},
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
|
|||||||
+23
-33
@@ -238,7 +238,9 @@
|
|||||||
"player": "Player",
|
"player": "Player",
|
||||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||||
"seekAndTiming": "Seek & Timing",
|
"seekAndTiming": "Seek & Timing",
|
||||||
"behavior": "Behavior"
|
"behavior": "Behavior",
|
||||||
|
"companionRemoteServer": "컴패니언 리모트 서버",
|
||||||
|
"companionRemoteServerDescription": "네트워크의 모바일 기기가 이 앱을 제어할 수 있도록 허용"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"hint": "영화, 시리즈, 음악 등을 검색하세요...",
|
"hint": "영화, 시리즈, 음악 등을 검색하세요...",
|
||||||
@@ -778,47 +780,35 @@
|
|||||||
"controlDesktop": "데스크톱 기기 제어",
|
"controlDesktop": "데스크톱 기기 제어",
|
||||||
"connectedTo": "${name}에 연결됨",
|
"connectedTo": "${name}에 연결됨",
|
||||||
"session": {
|
"session": {
|
||||||
"creatingSession": "원격 세션 생성 중...",
|
"startingServer": "원격 서버 시작 중...",
|
||||||
"failedToCreate": "원격 세션 생성 실패:",
|
"failedToCreate": "원격 서버를 시작하지 못했습니다:",
|
||||||
"noSession": "사용 가능한 세션이 없습니다",
|
|
||||||
"scanQrCode": "QR 코드 스캔",
|
|
||||||
"orEnterManually": "또는 수동으로 입력",
|
|
||||||
"hostAddress": "호스트 주소",
|
"hostAddress": "호스트 주소",
|
||||||
"sessionId": "세션 ID",
|
|
||||||
"pin": "PIN",
|
|
||||||
"connected": "연결됨",
|
"connected": "연결됨",
|
||||||
"waitingForConnection": "연결 대기 중...",
|
"serverRunning": "원격 서버 활성",
|
||||||
|
"serverStopped": "원격 서버 중지됨",
|
||||||
|
"serverRunningDescription": "네트워크의 모바일 기기가 이 앱을 검색하고 연결할 수 있습니다",
|
||||||
|
"serverStoppedDescription": "모바일 기기의 연결을 허용하려면 서버를 시작하세요",
|
||||||
"usePhoneToControl": "모바일 기기로 이 앱을 제어하세요",
|
"usePhoneToControl": "모바일 기기로 이 앱을 제어하세요",
|
||||||
"copiedToClipboard": "${label}이(가) 클립보드에 복사되었습니다",
|
"startServer": "서버 시작",
|
||||||
"copyToClipboard": "클립보드에 복사",
|
"stopServer": "서버 중지",
|
||||||
"newSession": "새 세션",
|
|
||||||
"minimize": "최소화"
|
"minimize": "최소화"
|
||||||
},
|
},
|
||||||
"pairing": {
|
"pairing": {
|
||||||
"scan": "스캔",
|
"pairWithDesktop": "데스크톱에 연결",
|
||||||
"manual": "수동",
|
"discoveryDescription": "같은 Plex 계정으로 Plezy를 실행 중인 네트워크의 기기가 자동으로 표시됩니다",
|
||||||
"pairWithDesktop": "데스크톱과 페어링",
|
|
||||||
"enterSessionDetails": "데스크톱 기기에 표시된 세션 정보를 입력하세요",
|
|
||||||
"hostAddressHint": "192.168.1.100:48632",
|
"hostAddressHint": "192.168.1.100:48632",
|
||||||
"sessionIdHint": "8자리 세션 ID 입력",
|
|
||||||
"pinHint": "6자리 PIN 입력",
|
|
||||||
"connecting": "연결 중...",
|
"connecting": "연결 중...",
|
||||||
"tips": "팁",
|
"searchingForDevices": "기기 검색 중...",
|
||||||
"tipDesktop": "데스크톱에서 Plezy를 열고 설정 또는 메뉴에서 Companion Remote를 활성화하세요",
|
"noDevicesFound": "네트워크에서 기기를 찾을 수 없습니다",
|
||||||
"tipScan": "스캔 탭을 사용하여 데스크톱의 QR 코드를 스캔하면 빠르게 페어링할 수 있습니다",
|
"noDevicesHint": "데스크톱에서 Plezy가 열려 있고 두 기기가 같은 WiFi 네트워크에 있는지 확인하세요",
|
||||||
"tipWifi": "두 기기가 동일한 WiFi 네트워크에 연결되어 있는지 확인하세요",
|
"availableDevices": "사용 가능한 기기",
|
||||||
"cameraPermissionRequired": "QR 코드를 스캔하려면 카메라 권한이 필요합니다.\n기기 설정에서 카메라 접근을 허용해 주세요.",
|
"manualConnection": "수동 연결",
|
||||||
"cameraError": "카메라를 시작할 수 없습니다: ${error}",
|
"cryptoInitFailed": "보안 연결을 초기화할 수 없습니다. Plex 계정에 로그인되어 있는지 확인하세요.",
|
||||||
"scanInstruction": "데스크톱에 표시된 QR 코드에 카메라를 향하세요",
|
|
||||||
"invalidQrCode": "유효하지 않은 QR 코드 형식",
|
|
||||||
"validationHostRequired": "호스트 주소를 입력하세요",
|
"validationHostRequired": "호스트 주소를 입력하세요",
|
||||||
"validationHostFormat": "IP:포트 형식이어야 합니다 (예: 192.168.1.100:48632)",
|
"validationHostFormat": "형식은 IP:포트여야 합니다 (예: 192.168.1.100:48632)",
|
||||||
"validationSessionIdRequired": "세션 ID를 입력하세요",
|
"connectionTimedOut": "연결 시간이 초과되었습니다. 두 기기가 같은 네트워크에 있는지 확인하세요.",
|
||||||
"validationSessionIdLength": "세션 ID는 8자리여야 합니다",
|
"sessionNotFound": "기기를 찾을 수 없습니다. 호스트에서 Plezy가 실행 중인지 확인하세요.",
|
||||||
"validationPinRequired": "PIN을 입력하세요",
|
"authFailed": "인증에 실패했습니다. 두 기기가 같은 Plex 계정을 사용하는지 확인하세요.",
|
||||||
"validationPinLength": "PIN은 6자리여야 합니다",
|
|
||||||
"connectionTimedOut": "연결 시간이 초과되었습니다. 세션 ID와 PIN을 확인하세요.",
|
|
||||||
"sessionNotFound": "세션을 찾을 수 없습니다. 자격 증명을 확인하세요.",
|
|
||||||
"failedToConnect": "연결 실패: ${error}"
|
"failedToConnect": "연결 실패: ${error}"
|
||||||
},
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
|
|||||||
+25
-35
@@ -238,7 +238,9 @@
|
|||||||
"player": "Player",
|
"player": "Player",
|
||||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||||
"seekAndTiming": "Seek & Timing",
|
"seekAndTiming": "Seek & Timing",
|
||||||
"behavior": "Behavior"
|
"behavior": "Behavior",
|
||||||
|
"companionRemoteServer": "Companion Remote-server",
|
||||||
|
"companionRemoteServerDescription": "Tillat mobilenheter på nettverket ditt å styre denne appen"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"hint": "Søk i filmer, serier, musikk...",
|
"hint": "Søk i filmer, serier, musikk...",
|
||||||
@@ -778,47 +780,35 @@
|
|||||||
"controlDesktop": "Kontroller en stasjonær enhet",
|
"controlDesktop": "Kontroller en stasjonær enhet",
|
||||||
"connectedTo": "Tilkoblet ${name}",
|
"connectedTo": "Tilkoblet ${name}",
|
||||||
"session": {
|
"session": {
|
||||||
"creatingSession": "Oppretter fjernøkt...",
|
"startingServer": "Starter fjernserver...",
|
||||||
"failedToCreate": "Kunne ikke opprette fjernøkt:",
|
"failedToCreate": "Kunne ikke starte fjernserver:",
|
||||||
"noSession": "Ingen økt tilgjengelig",
|
|
||||||
"scanQrCode": "Skann QR-kode",
|
|
||||||
"orEnterManually": "Eller skriv inn manuelt",
|
|
||||||
"hostAddress": "Vertsadresse",
|
"hostAddress": "Vertsadresse",
|
||||||
"sessionId": "Økt-ID",
|
|
||||||
"pin": "PIN",
|
|
||||||
"connected": "Tilkoblet",
|
"connected": "Tilkoblet",
|
||||||
"waitingForConnection": "Venter på tilkobling...",
|
"serverRunning": "Fjernserver aktiv",
|
||||||
"usePhoneToControl": "Bruk mobilenheten din til å kontrollere denne appen",
|
"serverStopped": "Fjernserver stoppet",
|
||||||
"copiedToClipboard": "${label} kopiert til utklippstavle",
|
"serverRunningDescription": "Mobilenheter på nettverket ditt kan oppdage og koble til denne appen",
|
||||||
"copyToClipboard": "Kopier til utklippstavle",
|
"serverStoppedDescription": "Start serveren for å la mobilenheter koble til",
|
||||||
"newSession": "Ny økt",
|
"usePhoneToControl": "Bruk mobilenheten din til å styre denne appen",
|
||||||
|
"startServer": "Start server",
|
||||||
|
"stopServer": "Stopp server",
|
||||||
"minimize": "Minimer"
|
"minimize": "Minimer"
|
||||||
},
|
},
|
||||||
"pairing": {
|
"pairing": {
|
||||||
"scan": "Skann",
|
"pairWithDesktop": "Koble til datamaskin",
|
||||||
"manual": "Manuell",
|
"discoveryDescription": "Enheter på nettverket ditt som kjører Plezy med samme Plex-konto vil vises automatisk",
|
||||||
"pairWithDesktop": "Koble til stasjonær",
|
|
||||||
"enterSessionDetails": "Skriv inn øktdetaljene som vises på den stasjonære enheten din",
|
|
||||||
"hostAddressHint": "192.168.1.100:48632",
|
"hostAddressHint": "192.168.1.100:48632",
|
||||||
"sessionIdHint": "Skriv inn 8-tegns økt-ID",
|
|
||||||
"pinHint": "Skriv inn 6-sifret PIN",
|
|
||||||
"connecting": "Kobler til...",
|
"connecting": "Kobler til...",
|
||||||
"tips": "Tips",
|
"searchingForDevices": "Søker etter enheter...",
|
||||||
"tipDesktop": "Åpne Plezy på datamaskinen din og aktiver Følgesvenn-fjernkontroll fra innstillinger eller meny",
|
"noDevicesFound": "Ingen enheter funnet på nettverket ditt",
|
||||||
"tipScan": "Bruk Skann-fanen for å raskt koble til ved å skanne QR-koden på datamaskinen din",
|
"noDevicesHint": "Sørg for at Plezy er åpen på datamaskinen din og at begge enhetene er på samme WiFi-nettverk",
|
||||||
"tipWifi": "Sørg for at begge enhetene er på samme WiFi-nettverk",
|
"availableDevices": "Tilgjengelige enheter",
|
||||||
"cameraPermissionRequired": "Kameratillatelse kreves for å skanne QR-koder.\nVennligst gi kameratilgang i enhetsinnstillingene.",
|
"manualConnection": "Manuell tilkobling",
|
||||||
"cameraError": "Kunne ikke starte kamera: ${error}",
|
"cryptoInitFailed": "Kunne ikke initialisere sikker tilkobling. Sørg for at du er logget inn på en Plex-konto.",
|
||||||
"scanInstruction": "Pek kameraet mot QR-koden som vises på datamaskinen din",
|
"validationHostRequired": "Vennligst oppgi vertsadresse",
|
||||||
"invalidQrCode": "Ugyldig QR-kodeformat",
|
"validationHostFormat": "Format må være IP:port (f.eks. 192.168.1.100:48632)",
|
||||||
"validationHostRequired": "Vennligst skriv inn vertsadresse",
|
"connectionTimedOut": "Tilkoblingen ble tidsavbrutt. Sørg for at begge enhetene er på samme nettverk.",
|
||||||
"validationHostFormat": "Formatet må være IP:port (f.eks. 192.168.1.100:48632)",
|
"sessionNotFound": "Kunne ikke finne enheten. Sørg for at Plezy kjører på verten.",
|
||||||
"validationSessionIdRequired": "Vennligst skriv inn en økt-ID",
|
"authFailed": "Autentisering mislyktes. Sørg for at begge enhetene bruker samme Plex-konto.",
|
||||||
"validationSessionIdLength": "Økt-ID må være 8 tegn",
|
|
||||||
"validationPinRequired": "Vennligst skriv inn en PIN",
|
|
||||||
"validationPinLength": "PIN må være 6 sifre",
|
|
||||||
"connectionTimedOut": "Tilkoblingen ble tidsavbrutt. Sjekk økt-ID og PIN.",
|
|
||||||
"sessionNotFound": "Kunne ikke finne økten. Sjekk legitimasjonen din.",
|
|
||||||
"failedToConnect": "Kunne ikke koble til: ${error}"
|
"failedToConnect": "Kunne ikke koble til: ${error}"
|
||||||
},
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
|
|||||||
+24
-34
@@ -238,7 +238,9 @@
|
|||||||
"player": "Player",
|
"player": "Player",
|
||||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||||
"seekAndTiming": "Seek & Timing",
|
"seekAndTiming": "Seek & Timing",
|
||||||
"behavior": "Behavior"
|
"behavior": "Behavior",
|
||||||
|
"companionRemoteServer": "Companion Remote-server",
|
||||||
|
"companionRemoteServerDescription": "Sta mobiele apparaten op je netwerk toe om deze app te bedienen"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"hint": "Zoek films, series, muziek...",
|
"hint": "Zoek films, series, muziek...",
|
||||||
@@ -778,48 +780,36 @@
|
|||||||
"controlDesktop": "Bedien een desktop-apparaat",
|
"controlDesktop": "Bedien een desktop-apparaat",
|
||||||
"connectedTo": "Verbonden met ${name}",
|
"connectedTo": "Verbonden met ${name}",
|
||||||
"session": {
|
"session": {
|
||||||
"creatingSession": "Externe sessie aanmaken...",
|
"startingServer": "Externe server starten...",
|
||||||
"failedToCreate": "Kan externe sessie niet aanmaken:",
|
"failedToCreate": "Kan externe server niet starten:",
|
||||||
"noSession": "Geen sessie beschikbaar",
|
|
||||||
"scanQrCode": "Scan QR-code",
|
|
||||||
"orEnterManually": "Of voer handmatig in",
|
|
||||||
"hostAddress": "Hostadres",
|
"hostAddress": "Hostadres",
|
||||||
"sessionId": "Sessie-ID",
|
|
||||||
"pin": "PIN",
|
|
||||||
"connected": "Verbonden",
|
"connected": "Verbonden",
|
||||||
"waitingForConnection": "Wachten op verbinding...",
|
"serverRunning": "Externe server actief",
|
||||||
|
"serverStopped": "Externe server gestopt",
|
||||||
|
"serverRunningDescription": "Mobiele apparaten op je netwerk kunnen deze app ontdekken en ermee verbinden",
|
||||||
|
"serverStoppedDescription": "Start de server om mobiele apparaten te laten verbinden",
|
||||||
"usePhoneToControl": "Gebruik je mobiele apparaat om deze app te bedienen",
|
"usePhoneToControl": "Gebruik je mobiele apparaat om deze app te bedienen",
|
||||||
"copiedToClipboard": "${label} gekopieerd naar klembord",
|
"startServer": "Server starten",
|
||||||
"copyToClipboard": "Kopieer naar klembord",
|
"stopServer": "Server stoppen",
|
||||||
"newSession": "Nieuwe sessie",
|
|
||||||
"minimize": "Minimaliseren"
|
"minimize": "Minimaliseren"
|
||||||
},
|
},
|
||||||
"pairing": {
|
"pairing": {
|
||||||
"scan": "Scannen",
|
"pairWithDesktop": "Verbinden met desktop",
|
||||||
"manual": "Handmatig",
|
"discoveryDescription": "Apparaten op je netwerk die Plezy gebruiken met hetzelfde Plex-account verschijnen automatisch",
|
||||||
"pairWithDesktop": "Koppelen met desktop",
|
|
||||||
"enterSessionDetails": "Voer de sessiegegevens in die op je desktop-apparaat worden getoond",
|
|
||||||
"hostAddressHint": "192.168.1.100:48632",
|
"hostAddressHint": "192.168.1.100:48632",
|
||||||
"sessionIdHint": "Voer 8-tekens sessie-ID in",
|
|
||||||
"pinHint": "Voer 6-cijferige PIN in",
|
|
||||||
"connecting": "Verbinden...",
|
"connecting": "Verbinden...",
|
||||||
"tips": "Tips",
|
"searchingForDevices": "Apparaten zoeken...",
|
||||||
"tipDesktop": "Open Plezy op je desktop en schakel Companion Remote in via instellingen of menu",
|
"noDevicesFound": "Geen apparaten gevonden op je netwerk",
|
||||||
"tipScan": "Gebruik het tabblad Scannen om snel te koppelen door de QR-code op je desktop te scannen",
|
"noDevicesHint": "Zorg ervoor dat Plezy geopend is op je desktop en dat beide apparaten op hetzelfde WiFi-netwerk zitten",
|
||||||
"tipWifi": "Zorg ervoor dat beide apparaten op hetzelfde WiFi-netwerk zitten",
|
"availableDevices": "Beschikbare apparaten",
|
||||||
"cameraPermissionRequired": "Cameratoestemming is vereist om QR-codes te scannen.\nGeef cameratoegang in je apparaatinstellingen.",
|
"manualConnection": "Handmatige verbinding",
|
||||||
"cameraError": "Kan camera niet starten: ${error}",
|
"cryptoInitFailed": "Kan beveiligde verbinding niet initialiseren. Zorg ervoor dat je bent ingelogd bij een Plex-account.",
|
||||||
"scanInstruction": "Richt je camera op de QR-code die op je desktop wordt getoond",
|
"validationHostRequired": "Voer het hostadres in",
|
||||||
"invalidQrCode": "Ongeldig QR-codeformaat",
|
|
||||||
"validationHostRequired": "Voer een hostadres in",
|
|
||||||
"validationHostFormat": "Formaat moet IP:poort zijn (bijv. 192.168.1.100:48632)",
|
"validationHostFormat": "Formaat moet IP:poort zijn (bijv. 192.168.1.100:48632)",
|
||||||
"validationSessionIdRequired": "Voer een sessie-ID in",
|
"connectionTimedOut": "Verbinding verlopen. Zorg ervoor dat beide apparaten op hetzelfde netwerk zitten.",
|
||||||
"validationSessionIdLength": "Sessie-ID moet 8 tekens zijn",
|
"sessionNotFound": "Apparaat niet gevonden. Zorg ervoor dat Plezy draait op de host.",
|
||||||
"validationPinRequired": "Voer een PIN in",
|
"authFailed": "Authenticatie mislukt. Zorg ervoor dat beide apparaten hetzelfde Plex-account gebruiken.",
|
||||||
"validationPinLength": "PIN moet 6 cijfers zijn",
|
"failedToConnect": "Kan niet verbinden: ${error}"
|
||||||
"connectionTimedOut": "Verbinding verlopen. Controleer de sessie-ID en PIN.",
|
|
||||||
"sessionNotFound": "Kan de sessie niet vinden. Controleer je gegevens.",
|
|
||||||
"failedToConnect": "Verbinden mislukt: ${error}"
|
|
||||||
},
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
"disconnectConfirm": "Wil je de verbinding met de externe sessie verbreken?",
|
"disconnectConfirm": "Wil je de verbinding met de externe sessie verbreken?",
|
||||||
|
|||||||
+23
-33
@@ -238,7 +238,9 @@
|
|||||||
"player": "Player",
|
"player": "Player",
|
||||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||||
"seekAndTiming": "Seek & Timing",
|
"seekAndTiming": "Seek & Timing",
|
||||||
"behavior": "Behavior"
|
"behavior": "Behavior",
|
||||||
|
"companionRemoteServer": "Serwer zdalnego sterowania",
|
||||||
|
"companionRemoteServerDescription": "Pozwól urządzeniom mobilnym w sieci sterować tą aplikacją"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"hint": "Szukaj filmów, seriali, muzyki...",
|
"hint": "Szukaj filmów, seriali, muzyki...",
|
||||||
@@ -778,47 +780,35 @@
|
|||||||
"controlDesktop": "Steruj urządzeniem desktop",
|
"controlDesktop": "Steruj urządzeniem desktop",
|
||||||
"connectedTo": "Połączono z ${name}",
|
"connectedTo": "Połączono z ${name}",
|
||||||
"session": {
|
"session": {
|
||||||
"creatingSession": "Tworzenie sesji zdalnej...",
|
"startingServer": "Uruchamianie serwera zdalnego...",
|
||||||
"failedToCreate": "Nie udało się utworzyć sesji zdalnej:",
|
"failedToCreate": "Nie udało się uruchomić serwera zdalnego:",
|
||||||
"noSession": "Brak dostępnej sesji",
|
|
||||||
"scanQrCode": "Zeskanuj kod QR",
|
|
||||||
"orEnterManually": "Lub wprowadź ręcznie",
|
|
||||||
"hostAddress": "Adres hosta",
|
"hostAddress": "Adres hosta",
|
||||||
"sessionId": "ID sesji",
|
|
||||||
"pin": "PIN",
|
|
||||||
"connected": "Połączono",
|
"connected": "Połączono",
|
||||||
"waitingForConnection": "Oczekiwanie na połączenie...",
|
"serverRunning": "Serwer zdalny aktywny",
|
||||||
"usePhoneToControl": "Użyj urządzenia mobilnego do sterowania tą aplikacją",
|
"serverStopped": "Serwer zdalny zatrzymany",
|
||||||
"copiedToClipboard": "${label} skopiowane do schowka",
|
"serverRunningDescription": "Urządzenia mobilne w sieci mogą wykryć tę aplikację i się z nią połączyć",
|
||||||
"copyToClipboard": "Kopiuj do schowka",
|
"serverStoppedDescription": "Uruchom serwer, aby umożliwić połączenie urządzeń mobilnych",
|
||||||
"newSession": "Nowa sesja",
|
"usePhoneToControl": "Użyj urządzenia mobilnego, aby sterować tą aplikacją",
|
||||||
|
"startServer": "Uruchom serwer",
|
||||||
|
"stopServer": "Zatrzymaj serwer",
|
||||||
"minimize": "Minimalizuj"
|
"minimize": "Minimalizuj"
|
||||||
},
|
},
|
||||||
"pairing": {
|
"pairing": {
|
||||||
"scan": "Skanuj",
|
"pairWithDesktop": "Połącz z komputerem",
|
||||||
"manual": "Ręcznie",
|
"discoveryDescription": "Urządzenia w sieci z Plezy na tym samym koncie Plex pojawią się automatycznie",
|
||||||
"pairWithDesktop": "Sparuj z komputerem",
|
|
||||||
"enterSessionDetails": "Wprowadź dane sesji wyświetlone na urządzeniu desktop",
|
|
||||||
"hostAddressHint": "192.168.1.100:48632",
|
"hostAddressHint": "192.168.1.100:48632",
|
||||||
"sessionIdHint": "Wprowadź 8-znakowe ID sesji",
|
|
||||||
"pinHint": "Wprowadź 6-cyfrowy PIN",
|
|
||||||
"connecting": "Łączenie...",
|
"connecting": "Łączenie...",
|
||||||
"tips": "Wskazówki",
|
"searchingForDevices": "Szukanie urządzeń...",
|
||||||
"tipDesktop": "Otwórz Plezy na komputerze i włącz Pilota zdalnego w ustawieniach lub menu",
|
"noDevicesFound": "Nie znaleziono urządzeń w sieci",
|
||||||
"tipScan": "Użyj zakładki Skanuj, aby szybko sparować skanując kod QR na komputerze",
|
"noDevicesHint": "Upewnij się, że Plezy jest otwarte na komputerze i oba urządzenia są w tej samej sieci WiFi",
|
||||||
"tipWifi": "Upewnij się, że oba urządzenia są w tej samej sieci WiFi",
|
"availableDevices": "Dostępne urządzenia",
|
||||||
"cameraPermissionRequired": "Uprawnienie do kamery jest wymagane do skanowania kodów QR.\nPrzyznaj dostęp do kamery w ustawieniach urządzenia.",
|
"manualConnection": "Połączenie ręczne",
|
||||||
"cameraError": "Nie udało się uruchomić kamery: ${error}",
|
"cryptoInitFailed": "Nie udało się zainicjować bezpiecznego połączenia. Upewnij się, że jesteś zalogowany na konto Plex.",
|
||||||
"scanInstruction": "Skieruj kamerę na kod QR wyświetlony na komputerze",
|
|
||||||
"invalidQrCode": "Nieprawidłowy format kodu QR",
|
|
||||||
"validationHostRequired": "Wprowadź adres hosta",
|
"validationHostRequired": "Wprowadź adres hosta",
|
||||||
"validationHostFormat": "Format musi być IP:port (np. 192.168.1.100:48632)",
|
"validationHostFormat": "Format musi być IP:port (np. 192.168.1.100:48632)",
|
||||||
"validationSessionIdRequired": "Wprowadź ID sesji",
|
"connectionTimedOut": "Przekroczono limit czasu połączenia. Upewnij się, że oba urządzenia są w tej samej sieci.",
|
||||||
"validationSessionIdLength": "ID sesji musi mieć 8 znaków",
|
"sessionNotFound": "Nie znaleziono urządzenia. Upewnij się, że Plezy działa na hoście.",
|
||||||
"validationPinRequired": "Wprowadź PIN",
|
"authFailed": "Uwierzytelnianie nie powiodło się. Upewnij się, że oba urządzenia używają tego samego konta Plex.",
|
||||||
"validationPinLength": "PIN musi mieć 6 cyfr",
|
|
||||||
"connectionTimedOut": "Upłynął czas połączenia. Sprawdź ID sesji i PIN.",
|
|
||||||
"sessionNotFound": "Nie znaleziono sesji. Sprawdź swoje dane.",
|
|
||||||
"failedToConnect": "Nie udało się połączyć: ${error}"
|
"failedToConnect": "Nie udało się połączyć: ${error}"
|
||||||
},
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
|
|||||||
+27
-37
@@ -238,7 +238,9 @@
|
|||||||
"player": "Player",
|
"player": "Player",
|
||||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||||
"seekAndTiming": "Seek & Timing",
|
"seekAndTiming": "Seek & Timing",
|
||||||
"behavior": "Behavior"
|
"behavior": "Behavior",
|
||||||
|
"companionRemoteServer": "Servidor de controlo remoto",
|
||||||
|
"companionRemoteServerDescription": "Permitir que dispositivos móveis na sua rede controlem esta aplicação"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"hint": "Buscar filmes, séries, músicas...",
|
"hint": "Buscar filmes, séries, músicas...",
|
||||||
@@ -778,47 +780,35 @@
|
|||||||
"controlDesktop": "Controlar um dispositivo desktop",
|
"controlDesktop": "Controlar um dispositivo desktop",
|
||||||
"connectedTo": "Conectado a ${name}",
|
"connectedTo": "Conectado a ${name}",
|
||||||
"session": {
|
"session": {
|
||||||
"creatingSession": "Criando sessão remota...",
|
"startingServer": "A iniciar servidor remoto...",
|
||||||
"failedToCreate": "Falha ao criar sessão remota:",
|
"failedToCreate": "Falha ao iniciar o servidor remoto:",
|
||||||
"noSession": "Nenhuma sessão disponível",
|
"hostAddress": "Endereço do host",
|
||||||
"scanQrCode": "Escanear QR Code",
|
|
||||||
"orEnterManually": "Ou insira manualmente",
|
|
||||||
"hostAddress": "Endereço do Host",
|
|
||||||
"sessionId": "ID da Sessão",
|
|
||||||
"pin": "PIN",
|
|
||||||
"connected": "Conectado",
|
"connected": "Conectado",
|
||||||
"waitingForConnection": "Aguardando conexão...",
|
"serverRunning": "Servidor remoto ativo",
|
||||||
"usePhoneToControl": "Use seu dispositivo móvel para controlar este app",
|
"serverStopped": "Servidor remoto parado",
|
||||||
"copiedToClipboard": "${label} copiado para a área de transferência",
|
"serverRunningDescription": "Dispositivos móveis na sua rede podem descobrir e conectar-se a esta aplicação",
|
||||||
"copyToClipboard": "Copiar para área de transferência",
|
"serverStoppedDescription": "Inicie o servidor para permitir que dispositivos móveis se conectem",
|
||||||
"newSession": "Nova Sessão",
|
"usePhoneToControl": "Use o seu dispositivo móvel para controlar esta aplicação",
|
||||||
|
"startServer": "Iniciar servidor",
|
||||||
|
"stopServer": "Parar servidor",
|
||||||
"minimize": "Minimizar"
|
"minimize": "Minimizar"
|
||||||
},
|
},
|
||||||
"pairing": {
|
"pairing": {
|
||||||
"scan": "Escanear",
|
"pairWithDesktop": "Conectar ao desktop",
|
||||||
"manual": "Manual",
|
"discoveryDescription": "Dispositivos na sua rede a executar Plezy com a mesma conta Plex aparecerão automaticamente",
|
||||||
"pairWithDesktop": "Parear com Desktop",
|
|
||||||
"enterSessionDetails": "Insira os detalhes da sessão exibidos no seu dispositivo desktop",
|
|
||||||
"hostAddressHint": "192.168.1.100:48632",
|
"hostAddressHint": "192.168.1.100:48632",
|
||||||
"sessionIdHint": "Insira o ID da sessão de 8 caracteres",
|
"connecting": "A conectar...",
|
||||||
"pinHint": "Insira o PIN de 6 dígitos",
|
"searchingForDevices": "A procurar dispositivos...",
|
||||||
"connecting": "Conectando...",
|
"noDevicesFound": "Nenhum dispositivo encontrado na sua rede",
|
||||||
"tips": "Dicas",
|
"noDevicesHint": "Certifique-se de que o Plezy está aberto no seu desktop e que ambos os dispositivos estão na mesma rede WiFi",
|
||||||
"tipDesktop": "Abra o Plezy no desktop e ative o Controle Remoto nas configurações ou menu",
|
"availableDevices": "Dispositivos disponíveis",
|
||||||
"tipScan": "Use a aba Escanear para parear rapidamente escaneando o QR code no seu desktop",
|
"manualConnection": "Conexão manual",
|
||||||
"tipWifi": "Certifique-se de que ambos os dispositivos estão na mesma rede WiFi",
|
"cryptoInitFailed": "Não foi possível inicializar a conexão segura. Certifique-se de que está conectado a uma conta Plex.",
|
||||||
"cameraPermissionRequired": "A permissão da câmera é necessária para escanear QR codes.\nConceda acesso à câmera nas configurações do dispositivo.",
|
"validationHostRequired": "Introduza o endereço do host",
|
||||||
"cameraError": "Não foi possível iniciar a câmera: ${error}",
|
"validationHostFormat": "O formato deve ser IP:porta (ex. 192.168.1.100:48632)",
|
||||||
"scanInstruction": "Aponte sua câmera para o QR code exibido no seu desktop",
|
"connectionTimedOut": "Tempo de conexão esgotado. Certifique-se de que ambos os dispositivos estão na mesma rede.",
|
||||||
"invalidQrCode": "Formato de QR code inválido",
|
"sessionNotFound": "Dispositivo não encontrado. Certifique-se de que o Plezy está em execução no host.",
|
||||||
"validationHostRequired": "Insira o endereço do host",
|
"authFailed": "Autenticação falhou. Certifique-se de que ambos os dispositivos usam a mesma conta Plex.",
|
||||||
"validationHostFormat": "O formato deve ser IP:porta (ex: 192.168.1.100:48632)",
|
|
||||||
"validationSessionIdRequired": "Insira um ID de sessão",
|
|
||||||
"validationSessionIdLength": "O ID da sessão deve ter 8 caracteres",
|
|
||||||
"validationPinRequired": "Insira um PIN",
|
|
||||||
"validationPinLength": "O PIN deve ter 6 dígitos",
|
|
||||||
"connectionTimedOut": "Tempo de conexão esgotado. Verifique o ID da sessão e o PIN.",
|
|
||||||
"sessionNotFound": "Sessão não encontrada. Verifique suas credenciais.",
|
|
||||||
"failedToConnect": "Falha ao conectar: ${error}"
|
"failedToConnect": "Falha ao conectar: ${error}"
|
||||||
},
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
|
|||||||
+22
-32
@@ -238,7 +238,9 @@
|
|||||||
"player": "Player",
|
"player": "Player",
|
||||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||||
"seekAndTiming": "Seek & Timing",
|
"seekAndTiming": "Seek & Timing",
|
||||||
"behavior": "Behavior"
|
"behavior": "Behavior",
|
||||||
|
"companionRemoteServer": "Сервер удалённого управления",
|
||||||
|
"companionRemoteServerDescription": "Разрешить мобильным устройствам в сети управлять этим приложением"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"hint": "Поиск фильмов, сериалов, музыки...",
|
"hint": "Поиск фильмов, сериалов, музыки...",
|
||||||
@@ -778,47 +780,35 @@
|
|||||||
"controlDesktop": "Управлять десктопным устройством",
|
"controlDesktop": "Управлять десктопным устройством",
|
||||||
"connectedTo": "Подключено к ${name}",
|
"connectedTo": "Подключено к ${name}",
|
||||||
"session": {
|
"session": {
|
||||||
"creatingSession": "Создание удалённой сессии...",
|
"startingServer": "Запуск удалённого сервера...",
|
||||||
"failedToCreate": "Не удалось создать удалённую сессию:",
|
"failedToCreate": "Не удалось запустить удалённый сервер:",
|
||||||
"noSession": "Нет доступных сессий",
|
|
||||||
"scanQrCode": "Сканировать QR-код",
|
|
||||||
"orEnterManually": "Или введите вручную",
|
|
||||||
"hostAddress": "Адрес хоста",
|
"hostAddress": "Адрес хоста",
|
||||||
"sessionId": "ID сессии",
|
|
||||||
"pin": "PIN",
|
|
||||||
"connected": "Подключено",
|
"connected": "Подключено",
|
||||||
"waitingForConnection": "Ожидание подключения...",
|
"serverRunning": "Удалённый сервер активен",
|
||||||
|
"serverStopped": "Удалённый сервер остановлен",
|
||||||
|
"serverRunningDescription": "Мобильные устройства в вашей сети могут обнаруживать это приложение и подключаться к нему",
|
||||||
|
"serverStoppedDescription": "Запустите сервер, чтобы разрешить подключение мобильных устройств",
|
||||||
"usePhoneToControl": "Используйте мобильное устройство для управления этим приложением",
|
"usePhoneToControl": "Используйте мобильное устройство для управления этим приложением",
|
||||||
"copiedToClipboard": "${label} скопировано в буфер обмена",
|
"startServer": "Запустить сервер",
|
||||||
"copyToClipboard": "Скопировать в буфер обмена",
|
"stopServer": "Остановить сервер",
|
||||||
"newSession": "Новая сессия",
|
|
||||||
"minimize": "Свернуть"
|
"minimize": "Свернуть"
|
||||||
},
|
},
|
||||||
"pairing": {
|
"pairing": {
|
||||||
"scan": "Сканировать",
|
"pairWithDesktop": "Подключиться к компьютеру",
|
||||||
"manual": "Вручную",
|
"discoveryDescription": "Устройства в вашей сети с Plezy на том же аккаунте Plex появятся автоматически",
|
||||||
"pairWithDesktop": "Сопряжение с ПК",
|
|
||||||
"enterSessionDetails": "Введите данные сессии, показанные на десктопном устройстве",
|
|
||||||
"hostAddressHint": "192.168.1.100:48632",
|
"hostAddressHint": "192.168.1.100:48632",
|
||||||
"sessionIdHint": "Введите 8-символьный ID сессии",
|
|
||||||
"pinHint": "Введите 6-значный PIN",
|
|
||||||
"connecting": "Подключение...",
|
"connecting": "Подключение...",
|
||||||
"tips": "Советы",
|
"searchingForDevices": "Поиск устройств...",
|
||||||
"tipDesktop": "Откройте Plezy на десктопе и включите пульт управления в настройках или меню",
|
"noDevicesFound": "Устройства в вашей сети не найдены",
|
||||||
"tipScan": "Используйте вкладку «Сканировать» для быстрого сопряжения через QR-код на десктопе",
|
"noDevicesHint": "Убедитесь, что Plezy открыт на вашем компьютере и оба устройства находятся в одной сети WiFi",
|
||||||
"tipWifi": "Убедитесь, что оба устройства находятся в одной WiFi-сети",
|
"availableDevices": "Доступные устройства",
|
||||||
"cameraPermissionRequired": "Для сканирования QR-кодов требуется разрешение на камеру.\nПредоставьте доступ к камере в настройках устройства.",
|
"manualConnection": "Ручное подключение",
|
||||||
"cameraError": "Не удалось запустить камеру: ${error}",
|
"cryptoInitFailed": "Не удалось инициализировать безопасное соединение. Убедитесь, что вы вошли в аккаунт Plex.",
|
||||||
"scanInstruction": "Наведите камеру на QR-код, показанный на десктопе",
|
|
||||||
"invalidQrCode": "Недопустимый формат QR-кода",
|
|
||||||
"validationHostRequired": "Введите адрес хоста",
|
"validationHostRequired": "Введите адрес хоста",
|
||||||
"validationHostFormat": "Формат должен быть IP:порт (например, 192.168.1.100:48632)",
|
"validationHostFormat": "Формат должен быть IP:порт (например, 192.168.1.100:48632)",
|
||||||
"validationSessionIdRequired": "Введите ID сессии",
|
"connectionTimedOut": "Время подключения истекло. Убедитесь, что оба устройства находятся в одной сети.",
|
||||||
"validationSessionIdLength": "ID сессии должен содержать 8 символов",
|
"sessionNotFound": "Устройство не найдено. Убедитесь, что Plezy запущен на хосте.",
|
||||||
"validationPinRequired": "Введите PIN",
|
"authFailed": "Ошибка аутентификации. Убедитесь, что оба устройства используют один и тот же аккаунт Plex.",
|
||||||
"validationPinLength": "PIN должен содержать 6 цифр",
|
|
||||||
"connectionTimedOut": "Время подключения истекло. Проверьте ID сессии и PIN.",
|
|
||||||
"sessionNotFound": "Сессия не найдена. Проверьте ваши учётные данные.",
|
|
||||||
"failedToConnect": "Не удалось подключиться: ${error}"
|
"failedToConnect": "Не удалось подключиться: ${error}"
|
||||||
},
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
/// To regenerate, run: `dart run slang`
|
/// To regenerate, run: `dart run slang`
|
||||||
///
|
///
|
||||||
/// Locales: 15
|
/// Locales: 15
|
||||||
/// Strings: 13110 (874 per locale)
|
/// Strings: 12960 (864 per locale)
|
||||||
///
|
///
|
||||||
/// Built on 2026-04-13 at 20:34 UTC
|
/// Built on 2026-04-14 at 10:29 UTC
|
||||||
|
|
||||||
// coverage:ignore-file
|
// coverage:ignore-file
|
||||||
// ignore_for_file: type=lint, unused_import
|
// ignore_for_file: type=lint, unused_import
|
||||||
|
|||||||
+52
-72
@@ -358,6 +358,8 @@ class _TranslationsSettingsDa implements TranslationsSettingsEn {
|
|||||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||||
@override String get seekAndTiming => 'Seek & Timing';
|
@override String get seekAndTiming => 'Seek & Timing';
|
||||||
@override String get behavior => 'Behavior';
|
@override String get behavior => 'Behavior';
|
||||||
|
@override String get companionRemoteServer => 'Companion Remote Server';
|
||||||
|
@override String get companionRemoteServerDescription => 'Tillad mobilenheder på dit netværk at styre denne app';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: search
|
// Path: search
|
||||||
@@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionDa implements TranslationsCompanionRemo
|
|||||||
final TranslationsDa _root; // ignore: unused_field
|
final TranslationsDa _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get creatingSession => 'Opretter fjernsession...';
|
@override String get startingServer => 'Starter fjernserver...';
|
||||||
@override String get failedToCreate => 'Kunne ikke oprette fjernsession:';
|
@override String get failedToCreate => 'Kunne ikke starte fjernserver:';
|
||||||
@override String get noSession => 'Ingen session tilgængelig';
|
@override String get hostAddress => 'Værtsadresse';
|
||||||
@override String get scanQrCode => 'Scan QR-kode';
|
|
||||||
@override String get orEnterManually => 'Eller indtast manuelt';
|
|
||||||
@override String get hostAddress => 'Værtadresse';
|
|
||||||
@override String get sessionId => 'Sessions-ID';
|
|
||||||
@override String get pin => 'PIN';
|
|
||||||
@override String get connected => 'Forbundet';
|
@override String get connected => 'Forbundet';
|
||||||
@override String get waitingForConnection => 'Venter på forbindelse...';
|
@override String get serverRunning => 'Fjernserver aktiv';
|
||||||
|
@override String get serverStopped => 'Fjernserver stoppet';
|
||||||
|
@override String get serverRunningDescription => 'Mobilenheder på dit netværk kan finde og oprette forbindelse til denne app';
|
||||||
|
@override String get serverStoppedDescription => 'Start serveren for at tillade mobilenheder at oprette forbindelse';
|
||||||
@override String get usePhoneToControl => 'Brug din mobilenhed til at styre denne app';
|
@override String get usePhoneToControl => 'Brug din mobilenhed til at styre denne app';
|
||||||
@override String copiedToClipboard({required Object label}) => '${label} kopieret til udklipsholder';
|
@override String get startServer => 'Start server';
|
||||||
@override String get copyToClipboard => 'Kopiér til udklipsholder';
|
@override String get stopServer => 'Stop server';
|
||||||
@override String get newSession => 'Ny session';
|
|
||||||
@override String get minimize => 'Minimér';
|
@override String get minimize => 'Minimér';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1298,31 +1297,22 @@ class _TranslationsCompanionRemotePairingDa implements TranslationsCompanionRemo
|
|||||||
final TranslationsDa _root; // ignore: unused_field
|
final TranslationsDa _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get scan => 'Scan';
|
@override String get pairWithDesktop => 'Opret forbindelse til computer';
|
||||||
@override String get manual => 'Manuel';
|
@override String get discoveryDescription => 'Enheder på dit netværk, der kører Plezy med den samme Plex-konto, vises automatisk';
|
||||||
@override String get pairWithDesktop => 'Par med desktop';
|
|
||||||
@override String get enterSessionDetails => 'Indtast sessionsdetaljerne vist på din desktopenhed';
|
|
||||||
@override String get hostAddressHint => '192.168.1.100:48632';
|
@override String get hostAddressHint => '192.168.1.100:48632';
|
||||||
@override String get sessionIdHint => 'Indtast 8-tegns sessions-ID';
|
@override String get connecting => 'Opretter forbindelse...';
|
||||||
@override String get pinHint => 'Indtast 6-cifret PIN';
|
@override String get searchingForDevices => 'Søger efter enheder...';
|
||||||
@override String get connecting => 'Forbinder...';
|
@override String get noDevicesFound => 'Ingen enheder fundet på dit netværk';
|
||||||
@override String get tips => 'Tips';
|
@override String get noDevicesHint => 'Sørg for, at Plezy er åben på din computer, og at begge enheder er på det samme WiFi-netværk';
|
||||||
@override String get tipDesktop => 'Åbn Plezy på din desktop og aktivér fjernbetjening fra indstillinger eller menu';
|
@override String get availableDevices => 'Tilgængelige enheder';
|
||||||
@override String get tipScan => 'Brug fanen Scan til hurtigt at parre ved at scanne QR-koden på din desktop';
|
@override String get manualConnection => 'Manuel forbindelse';
|
||||||
@override String get tipWifi => 'Sørg for, at begge enheder er på det samme WiFi-netværk';
|
@override String get cryptoInitFailed => 'Kunne ikke initialisere sikker forbindelse. Sørg for, at du er logget ind på en Plex-konto.';
|
||||||
@override String get cameraPermissionRequired => 'Kameratilladelse er påkrævet for at scanne QR-koder.\nGiv kameraadgang i enhedsindstillinger.';
|
@override String get validationHostRequired => 'Angiv venligst værtsadresse';
|
||||||
@override String cameraError({required Object error}) => 'Kunne ikke starte kamera: ${error}';
|
|
||||||
@override String get scanInstruction => 'Ret kameraet mod QR-koden vist på din desktop';
|
|
||||||
@override String get invalidQrCode => 'Ugyldigt QR-kodeformat';
|
|
||||||
@override String get validationHostRequired => 'Indtast værtadresse';
|
|
||||||
@override String get validationHostFormat => 'Format skal være IP:port (f.eks. 192.168.1.100:48632)';
|
@override String get validationHostFormat => 'Format skal være IP:port (f.eks. 192.168.1.100:48632)';
|
||||||
@override String get validationSessionIdRequired => 'Indtast et sessions-ID';
|
@override String get connectionTimedOut => 'Forbindelsen udløb. Sørg for, at begge enheder er på det samme netværk.';
|
||||||
@override String get validationSessionIdLength => 'Sessions-ID skal være 8 tegn';
|
@override String get sessionNotFound => 'Kunne ikke finde enheden. Sørg for, at Plezy kører på værten.';
|
||||||
@override String get validationPinRequired => 'Indtast en PIN';
|
@override String get authFailed => 'Godkendelse mislykkedes. Sørg for, at begge enheder bruger den samme Plex-konto.';
|
||||||
@override String get validationPinLength => 'PIN skal være 6 cifre';
|
@override String failedToConnect({required Object error}) => 'Kunne ikke oprette forbindelse: ${error}';
|
||||||
@override String get connectionTimedOut => 'Forbindelsen fik timeout. Tjek sessions-ID og PIN.';
|
|
||||||
@override String get sessionNotFound => 'Kunne ikke finde sessionen. Tjek dine oplysninger.';
|
|
||||||
@override String failedToConnect({required Object error}) => 'Kunne ikke forbinde: ${error}';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: companionRemote.remote
|
// Path: companionRemote.remote
|
||||||
@@ -1600,6 +1590,8 @@ extension on TranslationsDa {
|
|||||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||||
'settings.seekAndTiming' => 'Seek & Timing',
|
'settings.seekAndTiming' => 'Seek & Timing',
|
||||||
'settings.behavior' => 'Behavior',
|
'settings.behavior' => 'Behavior',
|
||||||
|
'settings.companionRemoteServer' => 'Companion Remote Server',
|
||||||
|
'settings.companionRemoteServerDescription' => 'Tillad mobilenheder på dit netværk at styre denne app',
|
||||||
'search.hint' => 'Søg film, serier, musik...',
|
'search.hint' => 'Søg film, serier, musik...',
|
||||||
'search.tryDifferentTerm' => 'Prøv en anden søgning',
|
'search.tryDifferentTerm' => 'Prøv en anden søgning',
|
||||||
'search.searchYourMedia' => 'Søg i dine medier',
|
'search.searchYourMedia' => 'Søg i dine medier',
|
||||||
@@ -1881,10 +1873,10 @@ extension on TranslationsDa {
|
|||||||
'libraries.groupings.seasons' => 'Sæsoner',
|
'libraries.groupings.seasons' => 'Sæsoner',
|
||||||
'libraries.groupings.episodes' => 'Episoder',
|
'libraries.groupings.episodes' => 'Episoder',
|
||||||
'libraries.groupings.folders' => 'Mapper',
|
'libraries.groupings.folders' => 'Mapper',
|
||||||
'about.title' => 'Om',
|
|
||||||
'about.openSourceLicenses' => 'Open source-licenser',
|
|
||||||
_ => null,
|
_ => null,
|
||||||
} ?? switch (path) {
|
} ?? switch (path) {
|
||||||
|
'about.title' => 'Om',
|
||||||
|
'about.openSourceLicenses' => 'Open source-licenser',
|
||||||
'about.versionLabel' => ({required Object version}) => 'Version ${version}',
|
'about.versionLabel' => ({required Object version}) => 'Version ${version}',
|
||||||
'about.appDescription' => 'En smuk Plex-klient til Flutter',
|
'about.appDescription' => 'En smuk Plex-klient til Flutter',
|
||||||
'about.viewLicensesDescription' => 'Se licenser for tredjepartsbiblioteker',
|
'about.viewLicensesDescription' => 'Se licenser for tredjepartsbiblioteker',
|
||||||
@@ -2076,46 +2068,34 @@ extension on TranslationsDa {
|
|||||||
'companionRemote.remoteControl' => 'Fjernbetjening',
|
'companionRemote.remoteControl' => 'Fjernbetjening',
|
||||||
'companionRemote.controlDesktop' => 'Styr en desktopenhed',
|
'companionRemote.controlDesktop' => 'Styr en desktopenhed',
|
||||||
'companionRemote.connectedTo' => ({required Object name}) => 'Forbundet til ${name}',
|
'companionRemote.connectedTo' => ({required Object name}) => 'Forbundet til ${name}',
|
||||||
'companionRemote.session.creatingSession' => 'Opretter fjernsession...',
|
'companionRemote.session.startingServer' => 'Starter fjernserver...',
|
||||||
'companionRemote.session.failedToCreate' => 'Kunne ikke oprette fjernsession:',
|
'companionRemote.session.failedToCreate' => 'Kunne ikke starte fjernserver:',
|
||||||
'companionRemote.session.noSession' => 'Ingen session tilgængelig',
|
'companionRemote.session.hostAddress' => 'Værtsadresse',
|
||||||
'companionRemote.session.scanQrCode' => 'Scan QR-kode',
|
|
||||||
'companionRemote.session.orEnterManually' => 'Eller indtast manuelt',
|
|
||||||
'companionRemote.session.hostAddress' => 'Værtadresse',
|
|
||||||
'companionRemote.session.sessionId' => 'Sessions-ID',
|
|
||||||
'companionRemote.session.pin' => 'PIN',
|
|
||||||
'companionRemote.session.connected' => 'Forbundet',
|
'companionRemote.session.connected' => 'Forbundet',
|
||||||
'companionRemote.session.waitingForConnection' => 'Venter på forbindelse...',
|
'companionRemote.session.serverRunning' => 'Fjernserver aktiv',
|
||||||
|
'companionRemote.session.serverStopped' => 'Fjernserver stoppet',
|
||||||
|
'companionRemote.session.serverRunningDescription' => 'Mobilenheder på dit netværk kan finde og oprette forbindelse til denne app',
|
||||||
|
'companionRemote.session.serverStoppedDescription' => 'Start serveren for at tillade mobilenheder at oprette forbindelse',
|
||||||
'companionRemote.session.usePhoneToControl' => 'Brug din mobilenhed til at styre denne app',
|
'companionRemote.session.usePhoneToControl' => 'Brug din mobilenhed til at styre denne app',
|
||||||
'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} kopieret til udklipsholder',
|
'companionRemote.session.startServer' => 'Start server',
|
||||||
'companionRemote.session.copyToClipboard' => 'Kopiér til udklipsholder',
|
'companionRemote.session.stopServer' => 'Stop server',
|
||||||
'companionRemote.session.newSession' => 'Ny session',
|
|
||||||
'companionRemote.session.minimize' => 'Minimér',
|
'companionRemote.session.minimize' => 'Minimér',
|
||||||
'companionRemote.pairing.scan' => 'Scan',
|
'companionRemote.pairing.pairWithDesktop' => 'Opret forbindelse til computer',
|
||||||
'companionRemote.pairing.manual' => 'Manuel',
|
'companionRemote.pairing.discoveryDescription' => 'Enheder på dit netværk, der kører Plezy med den samme Plex-konto, vises automatisk',
|
||||||
'companionRemote.pairing.pairWithDesktop' => 'Par med desktop',
|
|
||||||
'companionRemote.pairing.enterSessionDetails' => 'Indtast sessionsdetaljerne vist på din desktopenhed',
|
|
||||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||||
'companionRemote.pairing.sessionIdHint' => 'Indtast 8-tegns sessions-ID',
|
'companionRemote.pairing.connecting' => 'Opretter forbindelse...',
|
||||||
'companionRemote.pairing.pinHint' => 'Indtast 6-cifret PIN',
|
'companionRemote.pairing.searchingForDevices' => 'Søger efter enheder...',
|
||||||
'companionRemote.pairing.connecting' => 'Forbinder...',
|
'companionRemote.pairing.noDevicesFound' => 'Ingen enheder fundet på dit netværk',
|
||||||
'companionRemote.pairing.tips' => 'Tips',
|
'companionRemote.pairing.noDevicesHint' => 'Sørg for, at Plezy er åben på din computer, og at begge enheder er på det samme WiFi-netværk',
|
||||||
'companionRemote.pairing.tipDesktop' => 'Åbn Plezy på din desktop og aktivér fjernbetjening fra indstillinger eller menu',
|
'companionRemote.pairing.availableDevices' => 'Tilgængelige enheder',
|
||||||
'companionRemote.pairing.tipScan' => 'Brug fanen Scan til hurtigt at parre ved at scanne QR-koden på din desktop',
|
'companionRemote.pairing.manualConnection' => 'Manuel forbindelse',
|
||||||
'companionRemote.pairing.tipWifi' => 'Sørg for, at begge enheder er på det samme WiFi-netværk',
|
'companionRemote.pairing.cryptoInitFailed' => 'Kunne ikke initialisere sikker forbindelse. Sørg for, at du er logget ind på en Plex-konto.',
|
||||||
'companionRemote.pairing.cameraPermissionRequired' => 'Kameratilladelse er påkrævet for at scanne QR-koder.\nGiv kameraadgang i enhedsindstillinger.',
|
'companionRemote.pairing.validationHostRequired' => 'Angiv venligst værtsadresse',
|
||||||
'companionRemote.pairing.cameraError' => ({required Object error}) => 'Kunne ikke starte kamera: ${error}',
|
|
||||||
'companionRemote.pairing.scanInstruction' => 'Ret kameraet mod QR-koden vist på din desktop',
|
|
||||||
'companionRemote.pairing.invalidQrCode' => 'Ugyldigt QR-kodeformat',
|
|
||||||
'companionRemote.pairing.validationHostRequired' => 'Indtast værtadresse',
|
|
||||||
'companionRemote.pairing.validationHostFormat' => 'Format skal være IP:port (f.eks. 192.168.1.100:48632)',
|
'companionRemote.pairing.validationHostFormat' => 'Format skal være IP:port (f.eks. 192.168.1.100:48632)',
|
||||||
'companionRemote.pairing.validationSessionIdRequired' => 'Indtast et sessions-ID',
|
'companionRemote.pairing.connectionTimedOut' => 'Forbindelsen udløb. Sørg for, at begge enheder er på det samme netværk.',
|
||||||
'companionRemote.pairing.validationSessionIdLength' => 'Sessions-ID skal være 8 tegn',
|
'companionRemote.pairing.sessionNotFound' => 'Kunne ikke finde enheden. Sørg for, at Plezy kører på værten.',
|
||||||
'companionRemote.pairing.validationPinRequired' => 'Indtast en PIN',
|
'companionRemote.pairing.authFailed' => 'Godkendelse mislykkedes. Sørg for, at begge enheder bruger den samme Plex-konto.',
|
||||||
'companionRemote.pairing.validationPinLength' => 'PIN skal være 6 cifre',
|
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Kunne ikke oprette forbindelse: ${error}',
|
||||||
'companionRemote.pairing.connectionTimedOut' => 'Forbindelsen fik timeout. Tjek sessions-ID og PIN.',
|
|
||||||
'companionRemote.pairing.sessionNotFound' => 'Kunne ikke finde sessionen. Tjek dine oplysninger.',
|
|
||||||
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Kunne ikke forbinde: ${error}',
|
|
||||||
'companionRemote.remote.disconnectConfirm' => 'Vil du afbryde fra fjernsessionen?',
|
'companionRemote.remote.disconnectConfirm' => 'Vil du afbryde fra fjernsessionen?',
|
||||||
'companionRemote.remote.reconnecting' => 'Genopretter forbindelse...',
|
'companionRemote.remote.reconnecting' => 'Genopretter forbindelse...',
|
||||||
'companionRemote.remote.attemptOf' => ({required Object current}) => 'Forsøg ${current} af 5',
|
'companionRemote.remote.attemptOf' => ({required Object current}) => 'Forsøg ${current} af 5',
|
||||||
|
|||||||
+46
-66
@@ -358,6 +358,8 @@ class _TranslationsSettingsDe implements TranslationsSettingsEn {
|
|||||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||||
@override String get seekAndTiming => 'Seek & Timing';
|
@override String get seekAndTiming => 'Seek & Timing';
|
||||||
@override String get behavior => 'Behavior';
|
@override String get behavior => 'Behavior';
|
||||||
|
@override String get companionRemoteServer => 'Companion Remote Server';
|
||||||
|
@override String get companionRemoteServerDescription => 'Mobilgeräten in deinem Netzwerk erlauben, diese App zu steuern';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: search
|
// Path: search
|
||||||
@@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionDe implements TranslationsCompanionRemo
|
|||||||
final TranslationsDe _root; // ignore: unused_field
|
final TranslationsDe _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get creatingSession => 'Fernsteuerungssitzung wird erstellt...';
|
@override String get startingServer => 'Remote-Server wird gestartet...';
|
||||||
@override String get failedToCreate => 'Fernsteuerungssitzung konnte nicht erstellt werden:';
|
@override String get failedToCreate => 'Remote-Server konnte nicht gestartet werden:';
|
||||||
@override String get noSession => 'Keine Sitzung verfügbar';
|
|
||||||
@override String get scanQrCode => 'QR-Code scannen';
|
|
||||||
@override String get orEnterManually => 'Oder manuell eingeben';
|
|
||||||
@override String get hostAddress => 'Host-Adresse';
|
@override String get hostAddress => 'Host-Adresse';
|
||||||
@override String get sessionId => 'Sitzungs-ID';
|
|
||||||
@override String get pin => 'PIN';
|
|
||||||
@override String get connected => 'Verbunden';
|
@override String get connected => 'Verbunden';
|
||||||
@override String get waitingForConnection => 'Warte auf Verbindung...';
|
@override String get serverRunning => 'Remote-Server aktiv';
|
||||||
|
@override String get serverStopped => 'Remote-Server gestoppt';
|
||||||
|
@override String get serverRunningDescription => 'Mobilgeräte in deinem Netzwerk können diese App finden und sich verbinden';
|
||||||
|
@override String get serverStoppedDescription => 'Starte den Server, um Mobilgeräten die Verbindung zu ermöglichen';
|
||||||
@override String get usePhoneToControl => 'Verwende dein Mobilgerät, um diese App zu steuern';
|
@override String get usePhoneToControl => 'Verwende dein Mobilgerät, um diese App zu steuern';
|
||||||
@override String copiedToClipboard({required Object label}) => '${label} in Zwischenablage kopiert';
|
@override String get startServer => 'Server starten';
|
||||||
@override String get copyToClipboard => 'In Zwischenablage kopieren';
|
@override String get stopServer => 'Server stoppen';
|
||||||
@override String get newSession => 'Neue Sitzung';
|
|
||||||
@override String get minimize => 'Minimieren';
|
@override String get minimize => 'Minimieren';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingDe implements TranslationsCompanionRemo
|
|||||||
final TranslationsDe _root; // ignore: unused_field
|
final TranslationsDe _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get scan => 'Scannen';
|
@override String get pairWithDesktop => 'Mit Desktop verbinden';
|
||||||
@override String get manual => 'Manuell';
|
@override String get discoveryDescription => 'Geräte in deinem Netzwerk, die Plezy mit demselben Plex-Konto nutzen, erscheinen automatisch';
|
||||||
@override String get pairWithDesktop => 'Mit Desktop koppeln';
|
|
||||||
@override String get enterSessionDetails => 'Gib die Sitzungsdetails ein, die auf deinem Desktop-Gerät angezeigt werden';
|
|
||||||
@override String get hostAddressHint => '192.168.1.100:48632';
|
@override String get hostAddressHint => '192.168.1.100:48632';
|
||||||
@override String get sessionIdHint => '8-stellige Sitzungs-ID eingeben';
|
@override String get connecting => 'Verbinden...';
|
||||||
@override String get pinHint => '6-stellige PIN eingeben';
|
@override String get searchingForDevices => 'Suche nach Geräten...';
|
||||||
@override String get connecting => 'Verbinde...';
|
@override String get noDevicesFound => 'Keine Geräte in deinem Netzwerk gefunden';
|
||||||
@override String get tips => 'Tipps';
|
@override String get noDevicesHint => 'Stelle sicher, dass Plezy auf deinem Desktop geöffnet ist und beide Geräte im selben WLAN sind';
|
||||||
@override String get tipDesktop => 'Öffne Plezy auf deinem Desktop und aktiviere Companion Remote in den Einstellungen oder im Menü';
|
@override String get availableDevices => 'Verfügbare Geräte';
|
||||||
@override String get tipScan => 'Verwende den Scannen-Tab, um dich schnell per QR-Code auf deinem Desktop zu koppeln';
|
@override String get manualConnection => 'Manuelle Verbindung';
|
||||||
@override String get tipWifi => 'Stelle sicher, dass beide Geräte im selben WiFi-Netzwerk sind';
|
@override String get cryptoInitFailed => 'Sichere Verbindung konnte nicht initialisiert werden. Stelle sicher, dass du bei einem Plex-Konto angemeldet bist.';
|
||||||
@override String get cameraPermissionRequired => 'Kameraberechtigung wird zum Scannen von QR-Codes benötigt.\nBitte erteile den Kamerazugriff in den Geräteeinstellungen.';
|
|
||||||
@override String cameraError({required Object error}) => 'Kamera konnte nicht gestartet werden: ${error}';
|
|
||||||
@override String get scanInstruction => 'Richte deine Kamera auf den QR-Code auf deinem Desktop';
|
|
||||||
@override String get invalidQrCode => 'Ungültiges QR-Code-Format';
|
|
||||||
@override String get validationHostRequired => 'Bitte Host-Adresse eingeben';
|
@override String get validationHostRequired => 'Bitte Host-Adresse eingeben';
|
||||||
@override String get validationHostFormat => 'Format muss IP:Port sein (z.B. 192.168.1.100:48632)';
|
@override String get validationHostFormat => 'Format muss IP:Port sein (z.B. 192.168.1.100:48632)';
|
||||||
@override String get validationSessionIdRequired => 'Bitte Sitzungs-ID eingeben';
|
@override String get connectionTimedOut => 'Verbindung abgelaufen. Stelle sicher, dass beide Geräte im selben Netzwerk sind.';
|
||||||
@override String get validationSessionIdLength => 'Sitzungs-ID muss 8 Zeichen lang sein';
|
@override String get sessionNotFound => 'Gerät nicht gefunden. Stelle sicher, dass Plezy auf dem Host läuft.';
|
||||||
@override String get validationPinRequired => 'Bitte PIN eingeben';
|
@override String get authFailed => 'Authentifizierung fehlgeschlagen. Stelle sicher, dass beide Geräte dasselbe Plex-Konto verwenden.';
|
||||||
@override String get validationPinLength => 'PIN muss 6 Ziffern haben';
|
|
||||||
@override String get connectionTimedOut => 'Zeitüberschreitung. Bitte Sitzungs-ID und PIN überprüfen.';
|
|
||||||
@override String get sessionNotFound => 'Sitzung nicht gefunden. Bitte Zugangsdaten überprüfen.';
|
|
||||||
@override String failedToConnect({required Object error}) => 'Verbindung fehlgeschlagen: ${error}';
|
@override String failedToConnect({required Object error}) => 'Verbindung fehlgeschlagen: ${error}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1600,6 +1590,8 @@ extension on TranslationsDe {
|
|||||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||||
'settings.seekAndTiming' => 'Seek & Timing',
|
'settings.seekAndTiming' => 'Seek & Timing',
|
||||||
'settings.behavior' => 'Behavior',
|
'settings.behavior' => 'Behavior',
|
||||||
|
'settings.companionRemoteServer' => 'Companion Remote Server',
|
||||||
|
'settings.companionRemoteServerDescription' => 'Mobilgeräten in deinem Netzwerk erlauben, diese App zu steuern',
|
||||||
'search.hint' => 'Filme, Serien, Musik suchen...',
|
'search.hint' => 'Filme, Serien, Musik suchen...',
|
||||||
'search.tryDifferentTerm' => 'Anderen Suchbegriff versuchen',
|
'search.tryDifferentTerm' => 'Anderen Suchbegriff versuchen',
|
||||||
'search.searchYourMedia' => 'In den eigenen Medien suchen',
|
'search.searchYourMedia' => 'In den eigenen Medien suchen',
|
||||||
@@ -1881,10 +1873,10 @@ extension on TranslationsDe {
|
|||||||
'libraries.groupings.seasons' => 'Staffeln',
|
'libraries.groupings.seasons' => 'Staffeln',
|
||||||
'libraries.groupings.episodes' => 'Episoden',
|
'libraries.groupings.episodes' => 'Episoden',
|
||||||
'libraries.groupings.folders' => 'Ordner',
|
'libraries.groupings.folders' => 'Ordner',
|
||||||
'about.title' => 'Über',
|
|
||||||
'about.openSourceLicenses' => 'Open-Source-Lizenzen',
|
|
||||||
_ => null,
|
_ => null,
|
||||||
} ?? switch (path) {
|
} ?? switch (path) {
|
||||||
|
'about.title' => 'Über',
|
||||||
|
'about.openSourceLicenses' => 'Open-Source-Lizenzen',
|
||||||
'about.versionLabel' => ({required Object version}) => 'Version ${version}',
|
'about.versionLabel' => ({required Object version}) => 'Version ${version}',
|
||||||
'about.appDescription' => 'Ein schöner Plex-Client für Flutter',
|
'about.appDescription' => 'Ein schöner Plex-Client für Flutter',
|
||||||
'about.viewLicensesDescription' => 'Lizenzen von Drittanbieter-Bibliotheken anzeigen',
|
'about.viewLicensesDescription' => 'Lizenzen von Drittanbieter-Bibliotheken anzeigen',
|
||||||
@@ -2076,45 +2068,33 @@ extension on TranslationsDe {
|
|||||||
'companionRemote.remoteControl' => 'Fernsteuerung',
|
'companionRemote.remoteControl' => 'Fernsteuerung',
|
||||||
'companionRemote.controlDesktop' => 'Ein Desktop-Gerät steuern',
|
'companionRemote.controlDesktop' => 'Ein Desktop-Gerät steuern',
|
||||||
'companionRemote.connectedTo' => ({required Object name}) => 'Verbunden mit ${name}',
|
'companionRemote.connectedTo' => ({required Object name}) => 'Verbunden mit ${name}',
|
||||||
'companionRemote.session.creatingSession' => 'Fernsteuerungssitzung wird erstellt...',
|
'companionRemote.session.startingServer' => 'Remote-Server wird gestartet...',
|
||||||
'companionRemote.session.failedToCreate' => 'Fernsteuerungssitzung konnte nicht erstellt werden:',
|
'companionRemote.session.failedToCreate' => 'Remote-Server konnte nicht gestartet werden:',
|
||||||
'companionRemote.session.noSession' => 'Keine Sitzung verfügbar',
|
|
||||||
'companionRemote.session.scanQrCode' => 'QR-Code scannen',
|
|
||||||
'companionRemote.session.orEnterManually' => 'Oder manuell eingeben',
|
|
||||||
'companionRemote.session.hostAddress' => 'Host-Adresse',
|
'companionRemote.session.hostAddress' => 'Host-Adresse',
|
||||||
'companionRemote.session.sessionId' => 'Sitzungs-ID',
|
|
||||||
'companionRemote.session.pin' => 'PIN',
|
|
||||||
'companionRemote.session.connected' => 'Verbunden',
|
'companionRemote.session.connected' => 'Verbunden',
|
||||||
'companionRemote.session.waitingForConnection' => 'Warte auf Verbindung...',
|
'companionRemote.session.serverRunning' => 'Remote-Server aktiv',
|
||||||
|
'companionRemote.session.serverStopped' => 'Remote-Server gestoppt',
|
||||||
|
'companionRemote.session.serverRunningDescription' => 'Mobilgeräte in deinem Netzwerk können diese App finden und sich verbinden',
|
||||||
|
'companionRemote.session.serverStoppedDescription' => 'Starte den Server, um Mobilgeräten die Verbindung zu ermöglichen',
|
||||||
'companionRemote.session.usePhoneToControl' => 'Verwende dein Mobilgerät, um diese App zu steuern',
|
'companionRemote.session.usePhoneToControl' => 'Verwende dein Mobilgerät, um diese App zu steuern',
|
||||||
'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} in Zwischenablage kopiert',
|
'companionRemote.session.startServer' => 'Server starten',
|
||||||
'companionRemote.session.copyToClipboard' => 'In Zwischenablage kopieren',
|
'companionRemote.session.stopServer' => 'Server stoppen',
|
||||||
'companionRemote.session.newSession' => 'Neue Sitzung',
|
|
||||||
'companionRemote.session.minimize' => 'Minimieren',
|
'companionRemote.session.minimize' => 'Minimieren',
|
||||||
'companionRemote.pairing.scan' => 'Scannen',
|
'companionRemote.pairing.pairWithDesktop' => 'Mit Desktop verbinden',
|
||||||
'companionRemote.pairing.manual' => 'Manuell',
|
'companionRemote.pairing.discoveryDescription' => 'Geräte in deinem Netzwerk, die Plezy mit demselben Plex-Konto nutzen, erscheinen automatisch',
|
||||||
'companionRemote.pairing.pairWithDesktop' => 'Mit Desktop koppeln',
|
|
||||||
'companionRemote.pairing.enterSessionDetails' => 'Gib die Sitzungsdetails ein, die auf deinem Desktop-Gerät angezeigt werden',
|
|
||||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||||
'companionRemote.pairing.sessionIdHint' => '8-stellige Sitzungs-ID eingeben',
|
'companionRemote.pairing.connecting' => 'Verbinden...',
|
||||||
'companionRemote.pairing.pinHint' => '6-stellige PIN eingeben',
|
'companionRemote.pairing.searchingForDevices' => 'Suche nach Geräten...',
|
||||||
'companionRemote.pairing.connecting' => 'Verbinde...',
|
'companionRemote.pairing.noDevicesFound' => 'Keine Geräte in deinem Netzwerk gefunden',
|
||||||
'companionRemote.pairing.tips' => 'Tipps',
|
'companionRemote.pairing.noDevicesHint' => 'Stelle sicher, dass Plezy auf deinem Desktop geöffnet ist und beide Geräte im selben WLAN sind',
|
||||||
'companionRemote.pairing.tipDesktop' => 'Öffne Plezy auf deinem Desktop und aktiviere Companion Remote in den Einstellungen oder im Menü',
|
'companionRemote.pairing.availableDevices' => 'Verfügbare Geräte',
|
||||||
'companionRemote.pairing.tipScan' => 'Verwende den Scannen-Tab, um dich schnell per QR-Code auf deinem Desktop zu koppeln',
|
'companionRemote.pairing.manualConnection' => 'Manuelle Verbindung',
|
||||||
'companionRemote.pairing.tipWifi' => 'Stelle sicher, dass beide Geräte im selben WiFi-Netzwerk sind',
|
'companionRemote.pairing.cryptoInitFailed' => 'Sichere Verbindung konnte nicht initialisiert werden. Stelle sicher, dass du bei einem Plex-Konto angemeldet bist.',
|
||||||
'companionRemote.pairing.cameraPermissionRequired' => 'Kameraberechtigung wird zum Scannen von QR-Codes benötigt.\nBitte erteile den Kamerazugriff in den Geräteeinstellungen.',
|
|
||||||
'companionRemote.pairing.cameraError' => ({required Object error}) => 'Kamera konnte nicht gestartet werden: ${error}',
|
|
||||||
'companionRemote.pairing.scanInstruction' => 'Richte deine Kamera auf den QR-Code auf deinem Desktop',
|
|
||||||
'companionRemote.pairing.invalidQrCode' => 'Ungültiges QR-Code-Format',
|
|
||||||
'companionRemote.pairing.validationHostRequired' => 'Bitte Host-Adresse eingeben',
|
'companionRemote.pairing.validationHostRequired' => 'Bitte Host-Adresse eingeben',
|
||||||
'companionRemote.pairing.validationHostFormat' => 'Format muss IP:Port sein (z.B. 192.168.1.100:48632)',
|
'companionRemote.pairing.validationHostFormat' => 'Format muss IP:Port sein (z.B. 192.168.1.100:48632)',
|
||||||
'companionRemote.pairing.validationSessionIdRequired' => 'Bitte Sitzungs-ID eingeben',
|
'companionRemote.pairing.connectionTimedOut' => 'Verbindung abgelaufen. Stelle sicher, dass beide Geräte im selben Netzwerk sind.',
|
||||||
'companionRemote.pairing.validationSessionIdLength' => 'Sitzungs-ID muss 8 Zeichen lang sein',
|
'companionRemote.pairing.sessionNotFound' => 'Gerät nicht gefunden. Stelle sicher, dass Plezy auf dem Host läuft.',
|
||||||
'companionRemote.pairing.validationPinRequired' => 'Bitte PIN eingeben',
|
'companionRemote.pairing.authFailed' => 'Authentifizierung fehlgeschlagen. Stelle sicher, dass beide Geräte dasselbe Plex-Konto verwenden.',
|
||||||
'companionRemote.pairing.validationPinLength' => 'PIN muss 6 Ziffern haben',
|
|
||||||
'companionRemote.pairing.connectionTimedOut' => 'Zeitüberschreitung. Bitte Sitzungs-ID und PIN überprüfen.',
|
|
||||||
'companionRemote.pairing.sessionNotFound' => 'Sitzung nicht gefunden. Bitte Zugangsdaten überprüfen.',
|
|
||||||
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Verbindung fehlgeschlagen: ${error}',
|
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Verbindung fehlgeschlagen: ${error}',
|
||||||
'companionRemote.remote.disconnectConfirm' => 'Möchtest du die Verbindung zur Fernsteuerungssitzung trennen?',
|
'companionRemote.remote.disconnectConfirm' => 'Möchtest du die Verbindung zur Fernsteuerungssitzung trennen?',
|
||||||
'companionRemote.remote.reconnecting' => 'Verbindung wird wiederhergestellt...',
|
'companionRemote.remote.reconnecting' => 'Verbindung wird wiederhergestellt...',
|
||||||
|
|||||||
+70
-110
@@ -730,6 +730,12 @@ class TranslationsSettingsEn {
|
|||||||
/// en: 'Show what you're watching on Discord'
|
/// en: 'Show what you're watching on Discord'
|
||||||
String get discordRichPresenceDescription => 'Show what you\'re watching on Discord';
|
String get discordRichPresenceDescription => 'Show what you\'re watching on Discord';
|
||||||
|
|
||||||
|
/// en: 'Companion Remote Server'
|
||||||
|
String get companionRemoteServer => 'Companion Remote Server';
|
||||||
|
|
||||||
|
/// en: 'Allow mobile devices on your network to control this app'
|
||||||
|
String get companionRemoteServerDescription => 'Allow mobile devices on your network to control this app';
|
||||||
|
|
||||||
/// en: 'Auto Picture-in-Picture'
|
/// en: 'Auto Picture-in-Picture'
|
||||||
String get autoPip => 'Auto Picture-in-Picture';
|
String get autoPip => 'Auto Picture-in-Picture';
|
||||||
|
|
||||||
@@ -2892,47 +2898,38 @@ class TranslationsCompanionRemoteSessionEn {
|
|||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
|
|
||||||
/// en: 'Creating remote session...'
|
/// en: 'Starting remote server...'
|
||||||
String get creatingSession => 'Creating remote session...';
|
String get startingServer => 'Starting remote server...';
|
||||||
|
|
||||||
/// en: 'Failed to create remote session:'
|
/// en: 'Failed to start remote server:'
|
||||||
String get failedToCreate => 'Failed to create remote session:';
|
String get failedToCreate => 'Failed to start remote server:';
|
||||||
|
|
||||||
/// en: 'No session available'
|
|
||||||
String get noSession => 'No session available';
|
|
||||||
|
|
||||||
/// en: 'Scan QR Code'
|
|
||||||
String get scanQrCode => 'Scan QR Code';
|
|
||||||
|
|
||||||
/// en: 'Or enter manually'
|
|
||||||
String get orEnterManually => 'Or enter manually';
|
|
||||||
|
|
||||||
/// en: 'Host Address'
|
/// en: 'Host Address'
|
||||||
String get hostAddress => 'Host Address';
|
String get hostAddress => 'Host Address';
|
||||||
|
|
||||||
/// en: 'Session ID'
|
|
||||||
String get sessionId => 'Session ID';
|
|
||||||
|
|
||||||
/// en: 'PIN'
|
|
||||||
String get pin => 'PIN';
|
|
||||||
|
|
||||||
/// en: 'Connected'
|
/// en: 'Connected'
|
||||||
String get connected => 'Connected';
|
String get connected => 'Connected';
|
||||||
|
|
||||||
/// en: 'Waiting for connection...'
|
/// en: 'Remote server active'
|
||||||
String get waitingForConnection => 'Waiting for connection...';
|
String get serverRunning => 'Remote server active';
|
||||||
|
|
||||||
|
/// en: 'Remote server stopped'
|
||||||
|
String get serverStopped => 'Remote server stopped';
|
||||||
|
|
||||||
|
/// en: 'Mobile devices on your network can discover and connect to this app'
|
||||||
|
String get serverRunningDescription => 'Mobile devices on your network can discover and connect to this app';
|
||||||
|
|
||||||
|
/// en: 'Start the server to allow mobile devices to connect'
|
||||||
|
String get serverStoppedDescription => 'Start the server to allow mobile devices to connect';
|
||||||
|
|
||||||
/// en: 'Use your mobile device to control this app'
|
/// en: 'Use your mobile device to control this app'
|
||||||
String get usePhoneToControl => 'Use your mobile device to control this app';
|
String get usePhoneToControl => 'Use your mobile device to control this app';
|
||||||
|
|
||||||
/// en: '${label} copied to clipboard'
|
/// en: 'Start Server'
|
||||||
String copiedToClipboard({required Object label}) => '${label} copied to clipboard';
|
String get startServer => 'Start Server';
|
||||||
|
|
||||||
/// en: 'Copy to clipboard'
|
/// en: 'Stop Server'
|
||||||
String get copyToClipboard => 'Copy to clipboard';
|
String get stopServer => 'Stop Server';
|
||||||
|
|
||||||
/// en: 'New Session'
|
|
||||||
String get newSession => 'New Session';
|
|
||||||
|
|
||||||
/// en: 'Minimize'
|
/// en: 'Minimize'
|
||||||
String get minimize => 'Minimize';
|
String get minimize => 'Minimize';
|
||||||
@@ -2946,53 +2943,35 @@ class TranslationsCompanionRemotePairingEn {
|
|||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
|
|
||||||
/// en: 'Scan'
|
/// en: 'Connect to Desktop'
|
||||||
String get scan => 'Scan';
|
String get pairWithDesktop => 'Connect to Desktop';
|
||||||
|
|
||||||
/// en: 'Manual'
|
/// en: 'Devices on your network running Plezy with the same Plex account will appear automatically'
|
||||||
String get manual => 'Manual';
|
String get discoveryDescription => 'Devices on your network running Plezy with the same Plex account will appear automatically';
|
||||||
|
|
||||||
/// en: 'Pair with Desktop'
|
|
||||||
String get pairWithDesktop => 'Pair with Desktop';
|
|
||||||
|
|
||||||
/// en: 'Enter the session details shown on your desktop device'
|
|
||||||
String get enterSessionDetails => 'Enter the session details shown on your desktop device';
|
|
||||||
|
|
||||||
/// en: '192.168.1.100:48632'
|
/// en: '192.168.1.100:48632'
|
||||||
String get hostAddressHint => '192.168.1.100:48632';
|
String get hostAddressHint => '192.168.1.100:48632';
|
||||||
|
|
||||||
/// en: 'Enter 8-character session ID'
|
|
||||||
String get sessionIdHint => 'Enter 8-character session ID';
|
|
||||||
|
|
||||||
/// en: 'Enter 6-digit PIN'
|
|
||||||
String get pinHint => 'Enter 6-digit PIN';
|
|
||||||
|
|
||||||
/// en: 'Connecting...'
|
/// en: 'Connecting...'
|
||||||
String get connecting => 'Connecting...';
|
String get connecting => 'Connecting...';
|
||||||
|
|
||||||
/// en: 'Tips'
|
/// en: 'Looking for devices...'
|
||||||
String get tips => 'Tips';
|
String get searchingForDevices => 'Looking for devices...';
|
||||||
|
|
||||||
/// en: 'Open Plezy on your desktop and enable Companion Remote from settings or menu'
|
/// en: 'No devices found on your network'
|
||||||
String get tipDesktop => 'Open Plezy on your desktop and enable Companion Remote from settings or menu';
|
String get noDevicesFound => 'No devices found on your network';
|
||||||
|
|
||||||
/// en: 'Use the Scan tab to quickly pair by scanning the QR code on your desktop'
|
/// en: 'Make sure Plezy is open on your desktop and both devices are on the same WiFi network'
|
||||||
String get tipScan => 'Use the Scan tab to quickly pair by scanning the QR code on your desktop';
|
String get noDevicesHint => 'Make sure Plezy is open on your desktop and both devices are on the same WiFi network';
|
||||||
|
|
||||||
/// en: 'Make sure both devices are on the same WiFi network'
|
/// en: 'Available Devices'
|
||||||
String get tipWifi => 'Make sure both devices are on the same WiFi network';
|
String get availableDevices => 'Available Devices';
|
||||||
|
|
||||||
/// en: 'Camera permission is required to scan QR codes. Please grant camera access in your device settings.'
|
/// en: 'Manual Connection'
|
||||||
String get cameraPermissionRequired => 'Camera permission is required to scan QR codes.\nPlease grant camera access in your device settings.';
|
String get manualConnection => 'Manual Connection';
|
||||||
|
|
||||||
/// en: 'Could not start camera: ${error}'
|
/// en: 'Could not initialize secure connection. Make sure you are signed in to a Plex account.'
|
||||||
String cameraError({required Object error}) => 'Could not start camera: ${error}';
|
String get cryptoInitFailed => 'Could not initialize secure connection. Make sure you are signed in to a Plex account.';
|
||||||
|
|
||||||
/// en: 'Point your camera at the QR code shown on your desktop'
|
|
||||||
String get scanInstruction => 'Point your camera at the QR code shown on your desktop';
|
|
||||||
|
|
||||||
/// en: 'Invalid QR code format'
|
|
||||||
String get invalidQrCode => 'Invalid QR code format';
|
|
||||||
|
|
||||||
/// en: 'Please enter host address'
|
/// en: 'Please enter host address'
|
||||||
String get validationHostRequired => 'Please enter host address';
|
String get validationHostRequired => 'Please enter host address';
|
||||||
@@ -3000,23 +2979,14 @@ class TranslationsCompanionRemotePairingEn {
|
|||||||
/// en: 'Format must be IP:port (e.g., 192.168.1.100:48632)'
|
/// en: 'Format must be IP:port (e.g., 192.168.1.100:48632)'
|
||||||
String get validationHostFormat => 'Format must be IP:port (e.g., 192.168.1.100:48632)';
|
String get validationHostFormat => 'Format must be IP:port (e.g., 192.168.1.100:48632)';
|
||||||
|
|
||||||
/// en: 'Please enter a session ID'
|
/// en: 'Connection timed out. Make sure both devices are on the same network.'
|
||||||
String get validationSessionIdRequired => 'Please enter a session ID';
|
String get connectionTimedOut => 'Connection timed out. Make sure both devices are on the same network.';
|
||||||
|
|
||||||
/// en: 'Session ID must be 8 characters'
|
/// en: 'Could not find the device. Make sure Plezy is running on the host.'
|
||||||
String get validationSessionIdLength => 'Session ID must be 8 characters';
|
String get sessionNotFound => 'Could not find the device. Make sure Plezy is running on the host.';
|
||||||
|
|
||||||
/// en: 'Please enter a PIN'
|
/// en: 'Authentication failed. Make sure both devices are on the same Plex account.'
|
||||||
String get validationPinRequired => 'Please enter a PIN';
|
String get authFailed => 'Authentication failed. Make sure both devices are on the same Plex account.';
|
||||||
|
|
||||||
/// en: 'PIN must be 6 digits'
|
|
||||||
String get validationPinLength => 'PIN must be 6 digits';
|
|
||||||
|
|
||||||
/// en: 'Connection timed out. Please check the session ID and PIN.'
|
|
||||||
String get connectionTimedOut => 'Connection timed out. Please check the session ID and PIN.';
|
|
||||||
|
|
||||||
/// en: 'Could not find the session. Please check your credentials.'
|
|
||||||
String get sessionNotFound => 'Could not find the session. Please check your credentials.';
|
|
||||||
|
|
||||||
/// en: 'Failed to connect: ${error}'
|
/// en: 'Failed to connect: ${error}'
|
||||||
String failedToConnect({required Object error}) => 'Failed to connect: ${error}';
|
String failedToConnect({required Object error}) => 'Failed to connect: ${error}';
|
||||||
@@ -3325,6 +3295,8 @@ extension on Translations {
|
|||||||
'settings.maxVolumePercent' => ({required Object percent}) => '${percent}%',
|
'settings.maxVolumePercent' => ({required Object percent}) => '${percent}%',
|
||||||
'settings.discordRichPresence' => 'Discord Rich Presence',
|
'settings.discordRichPresence' => 'Discord Rich Presence',
|
||||||
'settings.discordRichPresenceDescription' => 'Show what you\'re watching on Discord',
|
'settings.discordRichPresenceDescription' => 'Show what you\'re watching on Discord',
|
||||||
|
'settings.companionRemoteServer' => 'Companion Remote Server',
|
||||||
|
'settings.companionRemoteServerDescription' => 'Allow mobile devices on your network to control this app',
|
||||||
'settings.autoPip' => 'Auto Picture-in-Picture',
|
'settings.autoPip' => 'Auto Picture-in-Picture',
|
||||||
'settings.autoPipDescription' => 'Automatically enter picture-in-picture when leaving the app during playback',
|
'settings.autoPipDescription' => 'Automatically enter picture-in-picture when leaving the app during playback',
|
||||||
'settings.matchContentFrameRate' => 'Match Content Frame Rate',
|
'settings.matchContentFrameRate' => 'Match Content Frame Rate',
|
||||||
@@ -3636,10 +3608,10 @@ extension on Translations {
|
|||||||
'libraries.groupings.seasons' => 'Seasons',
|
'libraries.groupings.seasons' => 'Seasons',
|
||||||
'libraries.groupings.episodes' => 'Episodes',
|
'libraries.groupings.episodes' => 'Episodes',
|
||||||
'libraries.groupings.folders' => 'Folders',
|
'libraries.groupings.folders' => 'Folders',
|
||||||
'about.title' => 'About',
|
|
||||||
'about.openSourceLicenses' => 'Open Source Licenses',
|
|
||||||
_ => null,
|
_ => null,
|
||||||
} ?? switch (path) {
|
} ?? switch (path) {
|
||||||
|
'about.title' => 'About',
|
||||||
|
'about.openSourceLicenses' => 'Open Source Licenses',
|
||||||
'about.versionLabel' => ({required Object version}) => 'Version ${version}',
|
'about.versionLabel' => ({required Object version}) => 'Version ${version}',
|
||||||
'about.appDescription' => 'A beautiful Plex client for Flutter',
|
'about.appDescription' => 'A beautiful Plex client for Flutter',
|
||||||
'about.viewLicensesDescription' => 'View licenses of third-party libraries',
|
'about.viewLicensesDescription' => 'View licenses of third-party libraries',
|
||||||
@@ -3831,45 +3803,33 @@ extension on Translations {
|
|||||||
'companionRemote.remoteControl' => 'Remote Control',
|
'companionRemote.remoteControl' => 'Remote Control',
|
||||||
'companionRemote.controlDesktop' => 'Control a desktop device',
|
'companionRemote.controlDesktop' => 'Control a desktop device',
|
||||||
'companionRemote.connectedTo' => ({required Object name}) => 'Connected to ${name}',
|
'companionRemote.connectedTo' => ({required Object name}) => 'Connected to ${name}',
|
||||||
'companionRemote.session.creatingSession' => 'Creating remote session...',
|
'companionRemote.session.startingServer' => 'Starting remote server...',
|
||||||
'companionRemote.session.failedToCreate' => 'Failed to create remote session:',
|
'companionRemote.session.failedToCreate' => 'Failed to start remote server:',
|
||||||
'companionRemote.session.noSession' => 'No session available',
|
|
||||||
'companionRemote.session.scanQrCode' => 'Scan QR Code',
|
|
||||||
'companionRemote.session.orEnterManually' => 'Or enter manually',
|
|
||||||
'companionRemote.session.hostAddress' => 'Host Address',
|
'companionRemote.session.hostAddress' => 'Host Address',
|
||||||
'companionRemote.session.sessionId' => 'Session ID',
|
|
||||||
'companionRemote.session.pin' => 'PIN',
|
|
||||||
'companionRemote.session.connected' => 'Connected',
|
'companionRemote.session.connected' => 'Connected',
|
||||||
'companionRemote.session.waitingForConnection' => 'Waiting for connection...',
|
'companionRemote.session.serverRunning' => 'Remote server active',
|
||||||
|
'companionRemote.session.serverStopped' => 'Remote server stopped',
|
||||||
|
'companionRemote.session.serverRunningDescription' => 'Mobile devices on your network can discover and connect to this app',
|
||||||
|
'companionRemote.session.serverStoppedDescription' => 'Start the server to allow mobile devices to connect',
|
||||||
'companionRemote.session.usePhoneToControl' => 'Use your mobile device to control this app',
|
'companionRemote.session.usePhoneToControl' => 'Use your mobile device to control this app',
|
||||||
'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} copied to clipboard',
|
'companionRemote.session.startServer' => 'Start Server',
|
||||||
'companionRemote.session.copyToClipboard' => 'Copy to clipboard',
|
'companionRemote.session.stopServer' => 'Stop Server',
|
||||||
'companionRemote.session.newSession' => 'New Session',
|
|
||||||
'companionRemote.session.minimize' => 'Minimize',
|
'companionRemote.session.minimize' => 'Minimize',
|
||||||
'companionRemote.pairing.scan' => 'Scan',
|
'companionRemote.pairing.pairWithDesktop' => 'Connect to Desktop',
|
||||||
'companionRemote.pairing.manual' => 'Manual',
|
'companionRemote.pairing.discoveryDescription' => 'Devices on your network running Plezy with the same Plex account will appear automatically',
|
||||||
'companionRemote.pairing.pairWithDesktop' => 'Pair with Desktop',
|
|
||||||
'companionRemote.pairing.enterSessionDetails' => 'Enter the session details shown on your desktop device',
|
|
||||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||||
'companionRemote.pairing.sessionIdHint' => 'Enter 8-character session ID',
|
|
||||||
'companionRemote.pairing.pinHint' => 'Enter 6-digit PIN',
|
|
||||||
'companionRemote.pairing.connecting' => 'Connecting...',
|
'companionRemote.pairing.connecting' => 'Connecting...',
|
||||||
'companionRemote.pairing.tips' => 'Tips',
|
'companionRemote.pairing.searchingForDevices' => 'Looking for devices...',
|
||||||
'companionRemote.pairing.tipDesktop' => 'Open Plezy on your desktop and enable Companion Remote from settings or menu',
|
'companionRemote.pairing.noDevicesFound' => 'No devices found on your network',
|
||||||
'companionRemote.pairing.tipScan' => 'Use the Scan tab to quickly pair by scanning the QR code on your desktop',
|
'companionRemote.pairing.noDevicesHint' => 'Make sure Plezy is open on your desktop and both devices are on the same WiFi network',
|
||||||
'companionRemote.pairing.tipWifi' => 'Make sure both devices are on the same WiFi network',
|
'companionRemote.pairing.availableDevices' => 'Available Devices',
|
||||||
'companionRemote.pairing.cameraPermissionRequired' => 'Camera permission is required to scan QR codes.\nPlease grant camera access in your device settings.',
|
'companionRemote.pairing.manualConnection' => 'Manual Connection',
|
||||||
'companionRemote.pairing.cameraError' => ({required Object error}) => 'Could not start camera: ${error}',
|
'companionRemote.pairing.cryptoInitFailed' => 'Could not initialize secure connection. Make sure you are signed in to a Plex account.',
|
||||||
'companionRemote.pairing.scanInstruction' => 'Point your camera at the QR code shown on your desktop',
|
|
||||||
'companionRemote.pairing.invalidQrCode' => 'Invalid QR code format',
|
|
||||||
'companionRemote.pairing.validationHostRequired' => 'Please enter host address',
|
'companionRemote.pairing.validationHostRequired' => 'Please enter host address',
|
||||||
'companionRemote.pairing.validationHostFormat' => 'Format must be IP:port (e.g., 192.168.1.100:48632)',
|
'companionRemote.pairing.validationHostFormat' => 'Format must be IP:port (e.g., 192.168.1.100:48632)',
|
||||||
'companionRemote.pairing.validationSessionIdRequired' => 'Please enter a session ID',
|
'companionRemote.pairing.connectionTimedOut' => 'Connection timed out. Make sure both devices are on the same network.',
|
||||||
'companionRemote.pairing.validationSessionIdLength' => 'Session ID must be 8 characters',
|
'companionRemote.pairing.sessionNotFound' => 'Could not find the device. Make sure Plezy is running on the host.',
|
||||||
'companionRemote.pairing.validationPinRequired' => 'Please enter a PIN',
|
'companionRemote.pairing.authFailed' => 'Authentication failed. Make sure both devices are on the same Plex account.',
|
||||||
'companionRemote.pairing.validationPinLength' => 'PIN must be 6 digits',
|
|
||||||
'companionRemote.pairing.connectionTimedOut' => 'Connection timed out. Please check the session ID and PIN.',
|
|
||||||
'companionRemote.pairing.sessionNotFound' => 'Could not find the session. Please check your credentials.',
|
|
||||||
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Failed to connect: ${error}',
|
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Failed to connect: ${error}',
|
||||||
'companionRemote.remote.disconnectConfirm' => 'Do you want to disconnect from the remote session?',
|
'companionRemote.remote.disconnectConfirm' => 'Do you want to disconnect from the remote session?',
|
||||||
'companionRemote.remote.reconnecting' => 'Reconnecting...',
|
'companionRemote.remote.reconnecting' => 'Reconnecting...',
|
||||||
|
|||||||
+50
-70
@@ -358,6 +358,8 @@ class _TranslationsSettingsEs implements TranslationsSettingsEn {
|
|||||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||||
@override String get seekAndTiming => 'Seek & Timing';
|
@override String get seekAndTiming => 'Seek & Timing';
|
||||||
@override String get behavior => 'Behavior';
|
@override String get behavior => 'Behavior';
|
||||||
|
@override String get companionRemoteServer => 'Servidor de control remoto';
|
||||||
|
@override String get companionRemoteServerDescription => 'Permitir que dispositivos móviles en tu red controlen esta aplicación';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: search
|
// Path: search
|
||||||
@@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionEs implements TranslationsCompanionRemo
|
|||||||
final TranslationsEs _root; // ignore: unused_field
|
final TranslationsEs _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get creatingSession => 'Creando sesión remota...';
|
@override String get startingServer => 'Iniciando servidor remoto...';
|
||||||
@override String get failedToCreate => 'Error al crear la sesión remota:';
|
@override String get failedToCreate => 'Error al iniciar el servidor remoto:';
|
||||||
@override String get noSession => 'No hay sesión disponible';
|
|
||||||
@override String get scanQrCode => 'Escanear código QR';
|
|
||||||
@override String get orEnterManually => 'O introducir manualmente';
|
|
||||||
@override String get hostAddress => 'Dirección del host';
|
@override String get hostAddress => 'Dirección del host';
|
||||||
@override String get sessionId => 'ID de sesión';
|
|
||||||
@override String get pin => 'PIN';
|
|
||||||
@override String get connected => 'Conectado';
|
@override String get connected => 'Conectado';
|
||||||
@override String get waitingForConnection => 'Esperando conexión...';
|
@override String get serverRunning => 'Servidor remoto activo';
|
||||||
@override String get usePhoneToControl => 'Usa tu dispositivo móvil para controlar esta app';
|
@override String get serverStopped => 'Servidor remoto detenido';
|
||||||
@override String copiedToClipboard({required Object label}) => '${label} copiado al portapapeles';
|
@override String get serverRunningDescription => 'Los dispositivos móviles en tu red pueden descubrir y conectarse a esta aplicación';
|
||||||
@override String get copyToClipboard => 'Copiar al portapapeles';
|
@override String get serverStoppedDescription => 'Inicia el servidor para permitir que los dispositivos móviles se conecten';
|
||||||
@override String get newSession => 'Nueva sesión';
|
@override String get usePhoneToControl => 'Usa tu dispositivo móvil para controlar esta aplicación';
|
||||||
|
@override String get startServer => 'Iniciar servidor';
|
||||||
|
@override String get stopServer => 'Detener servidor';
|
||||||
@override String get minimize => 'Minimizar';
|
@override String get minimize => 'Minimizar';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingEs implements TranslationsCompanionRemo
|
|||||||
final TranslationsEs _root; // ignore: unused_field
|
final TranslationsEs _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get scan => 'Escanear';
|
@override String get pairWithDesktop => 'Conectar al escritorio';
|
||||||
@override String get manual => 'Manual';
|
@override String get discoveryDescription => 'Los dispositivos en tu red que ejecutan Plezy con la misma cuenta de Plex aparecerán automáticamente';
|
||||||
@override String get pairWithDesktop => 'Emparejar con escritorio';
|
|
||||||
@override String get enterSessionDetails => 'Introduce los datos de la sesión que aparecen en tu dispositivo de escritorio';
|
|
||||||
@override String get hostAddressHint => '192.168.1.100:48632';
|
@override String get hostAddressHint => '192.168.1.100:48632';
|
||||||
@override String get sessionIdHint => 'Introduce ID de sesión de 8 caracteres';
|
|
||||||
@override String get pinHint => 'Introduce PIN de 6 dígitos';
|
|
||||||
@override String get connecting => 'Conectando...';
|
@override String get connecting => 'Conectando...';
|
||||||
@override String get tips => 'Consejos';
|
@override String get searchingForDevices => 'Buscando dispositivos...';
|
||||||
@override String get tipDesktop => 'Abre Plezy en tu escritorio y activa Companion Remote desde los ajustes o el menú';
|
@override String get noDevicesFound => 'No se encontraron dispositivos en tu red';
|
||||||
@override String get tipScan => 'Usa la pestaña Escanear para emparejar rápidamente escaneando el código QR de tu escritorio';
|
@override String get noDevicesHint => 'Asegúrate de que Plezy esté abierto en tu escritorio y que ambos dispositivos estén en la misma red WiFi';
|
||||||
@override String get tipWifi => 'Asegúrate de que ambos dispositivos estén en la misma red WiFi';
|
@override String get availableDevices => 'Dispositivos disponibles';
|
||||||
@override String get cameraPermissionRequired => 'Se necesita permiso de cámara para escanear códigos QR.\nPor favor, concede acceso a la cámara en los ajustes de tu dispositivo.';
|
@override String get manualConnection => 'Conexión manual';
|
||||||
@override String cameraError({required Object error}) => 'No se pudo iniciar la cámara: ${error}';
|
@override String get cryptoInitFailed => 'No se pudo inicializar la conexión segura. Asegúrate de haber iniciado sesión en una cuenta de Plex.';
|
||||||
@override String get scanInstruction => 'Apunta tu cámara al código QR que aparece en tu escritorio';
|
@override String get validationHostRequired => 'Ingresa la dirección del host';
|
||||||
@override String get invalidQrCode => 'Formato de código QR no válido';
|
@override String get validationHostFormat => 'El formato debe ser IP:puerto (ej. 192.168.1.100:48632)';
|
||||||
@override String get validationHostRequired => 'Por favor, introduce la dirección del host';
|
@override String get connectionTimedOut => 'Tiempo de conexión agotado. Asegúrate de que ambos dispositivos estén en la misma red.';
|
||||||
@override String get validationHostFormat => 'El formato debe ser IP:puerto (ej., 192.168.1.100:48632)';
|
@override String get sessionNotFound => 'No se encontró el dispositivo. Asegúrate de que Plezy esté ejecutándose en el host.';
|
||||||
@override String get validationSessionIdRequired => 'Por favor, introduce un ID de sesión';
|
@override String get authFailed => 'Autenticación fallida. Asegúrate de que ambos dispositivos usen la misma cuenta de Plex.';
|
||||||
@override String get validationSessionIdLength => 'El ID de sesión debe tener 8 caracteres';
|
|
||||||
@override String get validationPinRequired => 'Por favor, introduce un PIN';
|
|
||||||
@override String get validationPinLength => 'El PIN debe tener 6 dígitos';
|
|
||||||
@override String get connectionTimedOut => 'Tiempo de conexión agotado. Verifica el ID de sesión y el PIN.';
|
|
||||||
@override String get sessionNotFound => 'No se encontró la sesión. Verifica tus credenciales.';
|
|
||||||
@override String failedToConnect({required Object error}) => 'Error al conectar: ${error}';
|
@override String failedToConnect({required Object error}) => 'Error al conectar: ${error}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1600,6 +1590,8 @@ extension on TranslationsEs {
|
|||||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||||
'settings.seekAndTiming' => 'Seek & Timing',
|
'settings.seekAndTiming' => 'Seek & Timing',
|
||||||
'settings.behavior' => 'Behavior',
|
'settings.behavior' => 'Behavior',
|
||||||
|
'settings.companionRemoteServer' => 'Servidor de control remoto',
|
||||||
|
'settings.companionRemoteServerDescription' => 'Permitir que dispositivos móviles en tu red controlen esta aplicación',
|
||||||
'search.hint' => 'Buscar películas, series, música...',
|
'search.hint' => 'Buscar películas, series, música...',
|
||||||
'search.tryDifferentTerm' => 'Prueba con un término de búsqueda diferente',
|
'search.tryDifferentTerm' => 'Prueba con un término de búsqueda diferente',
|
||||||
'search.searchYourMedia' => 'Busca en tu contenido',
|
'search.searchYourMedia' => 'Busca en tu contenido',
|
||||||
@@ -1881,10 +1873,10 @@ extension on TranslationsEs {
|
|||||||
'libraries.groupings.seasons' => 'Temporadas',
|
'libraries.groupings.seasons' => 'Temporadas',
|
||||||
'libraries.groupings.episodes' => 'Episodios',
|
'libraries.groupings.episodes' => 'Episodios',
|
||||||
'libraries.groupings.folders' => 'Carpetas',
|
'libraries.groupings.folders' => 'Carpetas',
|
||||||
'about.title' => 'Acerca de',
|
|
||||||
'about.openSourceLicenses' => 'Licencias de Código Abierto',
|
|
||||||
_ => null,
|
_ => null,
|
||||||
} ?? switch (path) {
|
} ?? switch (path) {
|
||||||
|
'about.title' => 'Acerca de',
|
||||||
|
'about.openSourceLicenses' => 'Licencias de Código Abierto',
|
||||||
'about.versionLabel' => ({required Object version}) => 'Versión ${version}',
|
'about.versionLabel' => ({required Object version}) => 'Versión ${version}',
|
||||||
'about.appDescription' => 'Un cliente de Plex para Flutter',
|
'about.appDescription' => 'Un cliente de Plex para Flutter',
|
||||||
'about.viewLicensesDescription' => 'Ver licencias de librerías de terceros',
|
'about.viewLicensesDescription' => 'Ver licencias de librerías de terceros',
|
||||||
@@ -2076,45 +2068,33 @@ extension on TranslationsEs {
|
|||||||
'companionRemote.remoteControl' => 'Control remoto',
|
'companionRemote.remoteControl' => 'Control remoto',
|
||||||
'companionRemote.controlDesktop' => 'Controlar un dispositivo de escritorio',
|
'companionRemote.controlDesktop' => 'Controlar un dispositivo de escritorio',
|
||||||
'companionRemote.connectedTo' => ({required Object name}) => 'Conectado a ${name}',
|
'companionRemote.connectedTo' => ({required Object name}) => 'Conectado a ${name}',
|
||||||
'companionRemote.session.creatingSession' => 'Creando sesión remota...',
|
'companionRemote.session.startingServer' => 'Iniciando servidor remoto...',
|
||||||
'companionRemote.session.failedToCreate' => 'Error al crear la sesión remota:',
|
'companionRemote.session.failedToCreate' => 'Error al iniciar el servidor remoto:',
|
||||||
'companionRemote.session.noSession' => 'No hay sesión disponible',
|
|
||||||
'companionRemote.session.scanQrCode' => 'Escanear código QR',
|
|
||||||
'companionRemote.session.orEnterManually' => 'O introducir manualmente',
|
|
||||||
'companionRemote.session.hostAddress' => 'Dirección del host',
|
'companionRemote.session.hostAddress' => 'Dirección del host',
|
||||||
'companionRemote.session.sessionId' => 'ID de sesión',
|
|
||||||
'companionRemote.session.pin' => 'PIN',
|
|
||||||
'companionRemote.session.connected' => 'Conectado',
|
'companionRemote.session.connected' => 'Conectado',
|
||||||
'companionRemote.session.waitingForConnection' => 'Esperando conexión...',
|
'companionRemote.session.serverRunning' => 'Servidor remoto activo',
|
||||||
'companionRemote.session.usePhoneToControl' => 'Usa tu dispositivo móvil para controlar esta app',
|
'companionRemote.session.serverStopped' => 'Servidor remoto detenido',
|
||||||
'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} copiado al portapapeles',
|
'companionRemote.session.serverRunningDescription' => 'Los dispositivos móviles en tu red pueden descubrir y conectarse a esta aplicación',
|
||||||
'companionRemote.session.copyToClipboard' => 'Copiar al portapapeles',
|
'companionRemote.session.serverStoppedDescription' => 'Inicia el servidor para permitir que los dispositivos móviles se conecten',
|
||||||
'companionRemote.session.newSession' => 'Nueva sesión',
|
'companionRemote.session.usePhoneToControl' => 'Usa tu dispositivo móvil para controlar esta aplicación',
|
||||||
|
'companionRemote.session.startServer' => 'Iniciar servidor',
|
||||||
|
'companionRemote.session.stopServer' => 'Detener servidor',
|
||||||
'companionRemote.session.minimize' => 'Minimizar',
|
'companionRemote.session.minimize' => 'Minimizar',
|
||||||
'companionRemote.pairing.scan' => 'Escanear',
|
'companionRemote.pairing.pairWithDesktop' => 'Conectar al escritorio',
|
||||||
'companionRemote.pairing.manual' => 'Manual',
|
'companionRemote.pairing.discoveryDescription' => 'Los dispositivos en tu red que ejecutan Plezy con la misma cuenta de Plex aparecerán automáticamente',
|
||||||
'companionRemote.pairing.pairWithDesktop' => 'Emparejar con escritorio',
|
|
||||||
'companionRemote.pairing.enterSessionDetails' => 'Introduce los datos de la sesión que aparecen en tu dispositivo de escritorio',
|
|
||||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||||
'companionRemote.pairing.sessionIdHint' => 'Introduce ID de sesión de 8 caracteres',
|
|
||||||
'companionRemote.pairing.pinHint' => 'Introduce PIN de 6 dígitos',
|
|
||||||
'companionRemote.pairing.connecting' => 'Conectando...',
|
'companionRemote.pairing.connecting' => 'Conectando...',
|
||||||
'companionRemote.pairing.tips' => 'Consejos',
|
'companionRemote.pairing.searchingForDevices' => 'Buscando dispositivos...',
|
||||||
'companionRemote.pairing.tipDesktop' => 'Abre Plezy en tu escritorio y activa Companion Remote desde los ajustes o el menú',
|
'companionRemote.pairing.noDevicesFound' => 'No se encontraron dispositivos en tu red',
|
||||||
'companionRemote.pairing.tipScan' => 'Usa la pestaña Escanear para emparejar rápidamente escaneando el código QR de tu escritorio',
|
'companionRemote.pairing.noDevicesHint' => 'Asegúrate de que Plezy esté abierto en tu escritorio y que ambos dispositivos estén en la misma red WiFi',
|
||||||
'companionRemote.pairing.tipWifi' => 'Asegúrate de que ambos dispositivos estén en la misma red WiFi',
|
'companionRemote.pairing.availableDevices' => 'Dispositivos disponibles',
|
||||||
'companionRemote.pairing.cameraPermissionRequired' => 'Se necesita permiso de cámara para escanear códigos QR.\nPor favor, concede acceso a la cámara en los ajustes de tu dispositivo.',
|
'companionRemote.pairing.manualConnection' => 'Conexión manual',
|
||||||
'companionRemote.pairing.cameraError' => ({required Object error}) => 'No se pudo iniciar la cámara: ${error}',
|
'companionRemote.pairing.cryptoInitFailed' => 'No se pudo inicializar la conexión segura. Asegúrate de haber iniciado sesión en una cuenta de Plex.',
|
||||||
'companionRemote.pairing.scanInstruction' => 'Apunta tu cámara al código QR que aparece en tu escritorio',
|
'companionRemote.pairing.validationHostRequired' => 'Ingresa la dirección del host',
|
||||||
'companionRemote.pairing.invalidQrCode' => 'Formato de código QR no válido',
|
'companionRemote.pairing.validationHostFormat' => 'El formato debe ser IP:puerto (ej. 192.168.1.100:48632)',
|
||||||
'companionRemote.pairing.validationHostRequired' => 'Por favor, introduce la dirección del host',
|
'companionRemote.pairing.connectionTimedOut' => 'Tiempo de conexión agotado. Asegúrate de que ambos dispositivos estén en la misma red.',
|
||||||
'companionRemote.pairing.validationHostFormat' => 'El formato debe ser IP:puerto (ej., 192.168.1.100:48632)',
|
'companionRemote.pairing.sessionNotFound' => 'No se encontró el dispositivo. Asegúrate de que Plezy esté ejecutándose en el host.',
|
||||||
'companionRemote.pairing.validationSessionIdRequired' => 'Por favor, introduce un ID de sesión',
|
'companionRemote.pairing.authFailed' => 'Autenticación fallida. Asegúrate de que ambos dispositivos usen la misma cuenta de Plex.',
|
||||||
'companionRemote.pairing.validationSessionIdLength' => 'El ID de sesión debe tener 8 caracteres',
|
|
||||||
'companionRemote.pairing.validationPinRequired' => 'Por favor, introduce un PIN',
|
|
||||||
'companionRemote.pairing.validationPinLength' => 'El PIN debe tener 6 dígitos',
|
|
||||||
'companionRemote.pairing.connectionTimedOut' => 'Tiempo de conexión agotado. Verifica el ID de sesión y el PIN.',
|
|
||||||
'companionRemote.pairing.sessionNotFound' => 'No se encontró la sesión. Verifica tus credenciales.',
|
|
||||||
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Error al conectar: ${error}',
|
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Error al conectar: ${error}',
|
||||||
'companionRemote.remote.disconnectConfirm' => '¿Quieres desconectarte de la sesión remota?',
|
'companionRemote.remote.disconnectConfirm' => '¿Quieres desconectarte de la sesión remota?',
|
||||||
'companionRemote.remote.reconnecting' => 'Reconectando...',
|
'companionRemote.remote.reconnecting' => 'Reconectando...',
|
||||||
|
|||||||
+48
-68
@@ -358,6 +358,8 @@ class _TranslationsSettingsFr implements TranslationsSettingsEn {
|
|||||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||||
@override String get seekAndTiming => 'Seek & Timing';
|
@override String get seekAndTiming => 'Seek & Timing';
|
||||||
@override String get behavior => 'Behavior';
|
@override String get behavior => 'Behavior';
|
||||||
|
@override String get companionRemoteServer => 'Serveur de télécommande';
|
||||||
|
@override String get companionRemoteServerDescription => 'Autoriser les appareils mobiles de votre réseau à contrôler cette application';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: search
|
// Path: search
|
||||||
@@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionFr implements TranslationsCompanionRemo
|
|||||||
final TranslationsFr _root; // ignore: unused_field
|
final TranslationsFr _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get creatingSession => 'Création de la session distante...';
|
@override String get startingServer => 'Démarrage du serveur distant...';
|
||||||
@override String get failedToCreate => 'Échec de la création de la session distante :';
|
@override String get failedToCreate => 'Échec du démarrage du serveur distant :';
|
||||||
@override String get noSession => 'Aucune session disponible';
|
|
||||||
@override String get scanQrCode => 'Scanner le QR Code';
|
|
||||||
@override String get orEnterManually => 'Ou saisir manuellement';
|
|
||||||
@override String get hostAddress => 'Adresse de l\'hôte';
|
@override String get hostAddress => 'Adresse de l\'hôte';
|
||||||
@override String get sessionId => 'ID de session';
|
|
||||||
@override String get pin => 'PIN';
|
|
||||||
@override String get connected => 'Connecté';
|
@override String get connected => 'Connecté';
|
||||||
@override String get waitingForConnection => 'En attente de connexion...';
|
@override String get serverRunning => 'Serveur distant actif';
|
||||||
|
@override String get serverStopped => 'Serveur distant arrêté';
|
||||||
|
@override String get serverRunningDescription => 'Les appareils mobiles de votre réseau peuvent découvrir et se connecter à cette application';
|
||||||
|
@override String get serverStoppedDescription => 'Démarrez le serveur pour permettre aux appareils mobiles de se connecter';
|
||||||
@override String get usePhoneToControl => 'Utilisez votre appareil mobile pour contrôler cette application';
|
@override String get usePhoneToControl => 'Utilisez votre appareil mobile pour contrôler cette application';
|
||||||
@override String copiedToClipboard({required Object label}) => '${label} copié dans le presse-papiers';
|
@override String get startServer => 'Démarrer le serveur';
|
||||||
@override String get copyToClipboard => 'Copier dans le presse-papiers';
|
@override String get stopServer => 'Arrêter le serveur';
|
||||||
@override String get newSession => 'Nouvelle session';
|
|
||||||
@override String get minimize => 'Réduire';
|
@override String get minimize => 'Réduire';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingFr implements TranslationsCompanionRemo
|
|||||||
final TranslationsFr _root; // ignore: unused_field
|
final TranslationsFr _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get scan => 'Scanner';
|
@override String get pairWithDesktop => 'Se connecter au bureau';
|
||||||
@override String get manual => 'Manuel';
|
@override String get discoveryDescription => 'Les appareils de votre réseau exécutant Plezy avec le même compte Plex apparaîtront automatiquement';
|
||||||
@override String get pairWithDesktop => 'Jumeler avec un bureau';
|
|
||||||
@override String get enterSessionDetails => 'Saisissez les détails de la session affichés sur votre appareil de bureau';
|
|
||||||
@override String get hostAddressHint => '192.168.1.100:48632';
|
@override String get hostAddressHint => '192.168.1.100:48632';
|
||||||
@override String get sessionIdHint => 'Saisissez l\'ID de session à 8 caractères';
|
|
||||||
@override String get pinHint => 'Saisissez le PIN à 6 chiffres';
|
|
||||||
@override String get connecting => 'Connexion...';
|
@override String get connecting => 'Connexion...';
|
||||||
@override String get tips => 'Conseils';
|
@override String get searchingForDevices => 'Recherche d\'appareils...';
|
||||||
@override String get tipDesktop => 'Ouvrez Plezy sur votre bureau et activez Companion Remote depuis les paramètres ou le menu';
|
@override String get noDevicesFound => 'Aucun appareil trouvé sur votre réseau';
|
||||||
@override String get tipScan => 'Utilisez l\'onglet Scanner pour jumeler rapidement en scannant le QR code sur votre bureau';
|
@override String get noDevicesHint => 'Assurez-vous que Plezy est ouvert sur votre bureau et que les deux appareils sont sur le même réseau WiFi';
|
||||||
@override String get tipWifi => 'Assurez-vous que les deux appareils sont sur le même réseau WiFi';
|
@override String get availableDevices => 'Appareils disponibles';
|
||||||
@override String get cameraPermissionRequired => 'L\'autorisation de la caméra est requise pour scanner les QR codes.\nVeuillez accorder l\'accès à la caméra dans les paramètres de votre appareil.';
|
@override String get manualConnection => 'Connexion manuelle';
|
||||||
@override String cameraError({required Object error}) => 'Impossible de démarrer la caméra : ${error}';
|
@override String get cryptoInitFailed => 'Impossible d\'initialiser la connexion sécurisée. Assurez-vous d\'être connecté à un compte Plex.';
|
||||||
@override String get scanInstruction => 'Pointez votre caméra vers le QR code affiché sur votre bureau';
|
@override String get validationHostRequired => 'Veuillez entrer l\'adresse de l\'hôte';
|
||||||
@override String get invalidQrCode => 'Format de QR code invalide';
|
@override String get validationHostFormat => 'Le format doit être IP:port (ex. 192.168.1.100:48632)';
|
||||||
@override String get validationHostRequired => 'Veuillez saisir l\'adresse de l\'hôte';
|
@override String get connectionTimedOut => 'Délai de connexion dépassé. Assurez-vous que les deux appareils sont sur le même réseau.';
|
||||||
@override String get validationHostFormat => 'Le format doit être IP:port (ex : 192.168.1.100:48632)';
|
@override String get sessionNotFound => 'Appareil introuvable. Assurez-vous que Plezy est en cours d\'exécution sur l\'hôte.';
|
||||||
@override String get validationSessionIdRequired => 'Veuillez saisir un ID de session';
|
@override String get authFailed => 'Échec de l\'authentification. Assurez-vous que les deux appareils utilisent le même compte Plex.';
|
||||||
@override String get validationSessionIdLength => 'L\'ID de session doit contenir 8 caractères';
|
|
||||||
@override String get validationPinRequired => 'Veuillez saisir un PIN';
|
|
||||||
@override String get validationPinLength => 'Le PIN doit contenir 6 chiffres';
|
|
||||||
@override String get connectionTimedOut => 'Délai de connexion expiré. Veuillez vérifier l\'ID de session et le PIN.';
|
|
||||||
@override String get sessionNotFound => 'Session introuvable. Veuillez vérifier vos identifiants.';
|
|
||||||
@override String failedToConnect({required Object error}) => 'Échec de la connexion : ${error}';
|
@override String failedToConnect({required Object error}) => 'Échec de la connexion : ${error}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1600,6 +1590,8 @@ extension on TranslationsFr {
|
|||||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||||
'settings.seekAndTiming' => 'Seek & Timing',
|
'settings.seekAndTiming' => 'Seek & Timing',
|
||||||
'settings.behavior' => 'Behavior',
|
'settings.behavior' => 'Behavior',
|
||||||
|
'settings.companionRemoteServer' => 'Serveur de télécommande',
|
||||||
|
'settings.companionRemoteServerDescription' => 'Autoriser les appareils mobiles de votre réseau à contrôler cette application',
|
||||||
'search.hint' => 'Rechercher des films, des séries, de la musique...',
|
'search.hint' => 'Rechercher des films, des séries, de la musique...',
|
||||||
'search.tryDifferentTerm' => 'Essayez un autre terme de recherche',
|
'search.tryDifferentTerm' => 'Essayez un autre terme de recherche',
|
||||||
'search.searchYourMedia' => 'Rechercher dans vos médias',
|
'search.searchYourMedia' => 'Rechercher dans vos médias',
|
||||||
@@ -1881,10 +1873,10 @@ extension on TranslationsFr {
|
|||||||
'libraries.groupings.seasons' => 'Saisons',
|
'libraries.groupings.seasons' => 'Saisons',
|
||||||
'libraries.groupings.episodes' => 'Épisodes',
|
'libraries.groupings.episodes' => 'Épisodes',
|
||||||
'libraries.groupings.folders' => 'Dossiers',
|
'libraries.groupings.folders' => 'Dossiers',
|
||||||
'about.title' => 'À propos',
|
|
||||||
'about.openSourceLicenses' => 'Licences Open Source',
|
|
||||||
_ => null,
|
_ => null,
|
||||||
} ?? switch (path) {
|
} ?? switch (path) {
|
||||||
|
'about.title' => 'À propos',
|
||||||
|
'about.openSourceLicenses' => 'Licences Open Source',
|
||||||
'about.versionLabel' => ({required Object version}) => 'Version ${version}',
|
'about.versionLabel' => ({required Object version}) => 'Version ${version}',
|
||||||
'about.appDescription' => 'Un magnifique client Plex pour Flutter',
|
'about.appDescription' => 'Un magnifique client Plex pour Flutter',
|
||||||
'about.viewLicensesDescription' => 'Afficher les licences des bibliothèques tierces',
|
'about.viewLicensesDescription' => 'Afficher les licences des bibliothèques tierces',
|
||||||
@@ -2076,45 +2068,33 @@ extension on TranslationsFr {
|
|||||||
'companionRemote.remoteControl' => 'Télécommande',
|
'companionRemote.remoteControl' => 'Télécommande',
|
||||||
'companionRemote.controlDesktop' => 'Contrôler un appareil de bureau',
|
'companionRemote.controlDesktop' => 'Contrôler un appareil de bureau',
|
||||||
'companionRemote.connectedTo' => ({required Object name}) => 'Connecté à ${name}',
|
'companionRemote.connectedTo' => ({required Object name}) => 'Connecté à ${name}',
|
||||||
'companionRemote.session.creatingSession' => 'Création de la session distante...',
|
'companionRemote.session.startingServer' => 'Démarrage du serveur distant...',
|
||||||
'companionRemote.session.failedToCreate' => 'Échec de la création de la session distante :',
|
'companionRemote.session.failedToCreate' => 'Échec du démarrage du serveur distant :',
|
||||||
'companionRemote.session.noSession' => 'Aucune session disponible',
|
|
||||||
'companionRemote.session.scanQrCode' => 'Scanner le QR Code',
|
|
||||||
'companionRemote.session.orEnterManually' => 'Ou saisir manuellement',
|
|
||||||
'companionRemote.session.hostAddress' => 'Adresse de l\'hôte',
|
'companionRemote.session.hostAddress' => 'Adresse de l\'hôte',
|
||||||
'companionRemote.session.sessionId' => 'ID de session',
|
|
||||||
'companionRemote.session.pin' => 'PIN',
|
|
||||||
'companionRemote.session.connected' => 'Connecté',
|
'companionRemote.session.connected' => 'Connecté',
|
||||||
'companionRemote.session.waitingForConnection' => 'En attente de connexion...',
|
'companionRemote.session.serverRunning' => 'Serveur distant actif',
|
||||||
|
'companionRemote.session.serverStopped' => 'Serveur distant arrêté',
|
||||||
|
'companionRemote.session.serverRunningDescription' => 'Les appareils mobiles de votre réseau peuvent découvrir et se connecter à cette application',
|
||||||
|
'companionRemote.session.serverStoppedDescription' => 'Démarrez le serveur pour permettre aux appareils mobiles de se connecter',
|
||||||
'companionRemote.session.usePhoneToControl' => 'Utilisez votre appareil mobile pour contrôler cette application',
|
'companionRemote.session.usePhoneToControl' => 'Utilisez votre appareil mobile pour contrôler cette application',
|
||||||
'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} copié dans le presse-papiers',
|
'companionRemote.session.startServer' => 'Démarrer le serveur',
|
||||||
'companionRemote.session.copyToClipboard' => 'Copier dans le presse-papiers',
|
'companionRemote.session.stopServer' => 'Arrêter le serveur',
|
||||||
'companionRemote.session.newSession' => 'Nouvelle session',
|
|
||||||
'companionRemote.session.minimize' => 'Réduire',
|
'companionRemote.session.minimize' => 'Réduire',
|
||||||
'companionRemote.pairing.scan' => 'Scanner',
|
'companionRemote.pairing.pairWithDesktop' => 'Se connecter au bureau',
|
||||||
'companionRemote.pairing.manual' => 'Manuel',
|
'companionRemote.pairing.discoveryDescription' => 'Les appareils de votre réseau exécutant Plezy avec le même compte Plex apparaîtront automatiquement',
|
||||||
'companionRemote.pairing.pairWithDesktop' => 'Jumeler avec un bureau',
|
|
||||||
'companionRemote.pairing.enterSessionDetails' => 'Saisissez les détails de la session affichés sur votre appareil de bureau',
|
|
||||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||||
'companionRemote.pairing.sessionIdHint' => 'Saisissez l\'ID de session à 8 caractères',
|
|
||||||
'companionRemote.pairing.pinHint' => 'Saisissez le PIN à 6 chiffres',
|
|
||||||
'companionRemote.pairing.connecting' => 'Connexion...',
|
'companionRemote.pairing.connecting' => 'Connexion...',
|
||||||
'companionRemote.pairing.tips' => 'Conseils',
|
'companionRemote.pairing.searchingForDevices' => 'Recherche d\'appareils...',
|
||||||
'companionRemote.pairing.tipDesktop' => 'Ouvrez Plezy sur votre bureau et activez Companion Remote depuis les paramètres ou le menu',
|
'companionRemote.pairing.noDevicesFound' => 'Aucun appareil trouvé sur votre réseau',
|
||||||
'companionRemote.pairing.tipScan' => 'Utilisez l\'onglet Scanner pour jumeler rapidement en scannant le QR code sur votre bureau',
|
'companionRemote.pairing.noDevicesHint' => 'Assurez-vous que Plezy est ouvert sur votre bureau et que les deux appareils sont sur le même réseau WiFi',
|
||||||
'companionRemote.pairing.tipWifi' => 'Assurez-vous que les deux appareils sont sur le même réseau WiFi',
|
'companionRemote.pairing.availableDevices' => 'Appareils disponibles',
|
||||||
'companionRemote.pairing.cameraPermissionRequired' => 'L\'autorisation de la caméra est requise pour scanner les QR codes.\nVeuillez accorder l\'accès à la caméra dans les paramètres de votre appareil.',
|
'companionRemote.pairing.manualConnection' => 'Connexion manuelle',
|
||||||
'companionRemote.pairing.cameraError' => ({required Object error}) => 'Impossible de démarrer la caméra : ${error}',
|
'companionRemote.pairing.cryptoInitFailed' => 'Impossible d\'initialiser la connexion sécurisée. Assurez-vous d\'être connecté à un compte Plex.',
|
||||||
'companionRemote.pairing.scanInstruction' => 'Pointez votre caméra vers le QR code affiché sur votre bureau',
|
'companionRemote.pairing.validationHostRequired' => 'Veuillez entrer l\'adresse de l\'hôte',
|
||||||
'companionRemote.pairing.invalidQrCode' => 'Format de QR code invalide',
|
'companionRemote.pairing.validationHostFormat' => 'Le format doit être IP:port (ex. 192.168.1.100:48632)',
|
||||||
'companionRemote.pairing.validationHostRequired' => 'Veuillez saisir l\'adresse de l\'hôte',
|
'companionRemote.pairing.connectionTimedOut' => 'Délai de connexion dépassé. Assurez-vous que les deux appareils sont sur le même réseau.',
|
||||||
'companionRemote.pairing.validationHostFormat' => 'Le format doit être IP:port (ex : 192.168.1.100:48632)',
|
'companionRemote.pairing.sessionNotFound' => 'Appareil introuvable. Assurez-vous que Plezy est en cours d\'exécution sur l\'hôte.',
|
||||||
'companionRemote.pairing.validationSessionIdRequired' => 'Veuillez saisir un ID de session',
|
'companionRemote.pairing.authFailed' => 'Échec de l\'authentification. Assurez-vous que les deux appareils utilisent le même compte Plex.',
|
||||||
'companionRemote.pairing.validationSessionIdLength' => 'L\'ID de session doit contenir 8 caractères',
|
|
||||||
'companionRemote.pairing.validationPinRequired' => 'Veuillez saisir un PIN',
|
|
||||||
'companionRemote.pairing.validationPinLength' => 'Le PIN doit contenir 6 chiffres',
|
|
||||||
'companionRemote.pairing.connectionTimedOut' => 'Délai de connexion expiré. Veuillez vérifier l\'ID de session et le PIN.',
|
|
||||||
'companionRemote.pairing.sessionNotFound' => 'Session introuvable. Veuillez vérifier vos identifiants.',
|
|
||||||
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Échec de la connexion : ${error}',
|
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Échec de la connexion : ${error}',
|
||||||
'companionRemote.remote.disconnectConfirm' => 'Voulez-vous vous déconnecter de la session distante ?',
|
'companionRemote.remote.disconnectConfirm' => 'Voulez-vous vous déconnecter de la session distante ?',
|
||||||
'companionRemote.remote.reconnecting' => 'Reconnexion...',
|
'companionRemote.remote.reconnecting' => 'Reconnexion...',
|
||||||
|
|||||||
+44
-64
@@ -358,6 +358,8 @@ class _TranslationsSettingsIt implements TranslationsSettingsEn {
|
|||||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||||
@override String get seekAndTiming => 'Seek & Timing';
|
@override String get seekAndTiming => 'Seek & Timing';
|
||||||
@override String get behavior => 'Behavior';
|
@override String get behavior => 'Behavior';
|
||||||
|
@override String get companionRemoteServer => 'Server telecomando';
|
||||||
|
@override String get companionRemoteServerDescription => 'Consenti ai dispositivi mobili nella tua rete di controllare questa app';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: search
|
// Path: search
|
||||||
@@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionIt implements TranslationsCompanionRemo
|
|||||||
final TranslationsIt _root; // ignore: unused_field
|
final TranslationsIt _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get creatingSession => 'Creazione sessione remota...';
|
@override String get startingServer => 'Avvio del server remoto...';
|
||||||
@override String get failedToCreate => 'Impossibile creare la sessione remota:';
|
@override String get failedToCreate => 'Impossibile avviare il server remoto:';
|
||||||
@override String get noSession => 'Nessuna sessione disponibile';
|
|
||||||
@override String get scanQrCode => 'Scansiona QR Code';
|
|
||||||
@override String get orEnterManually => 'Oppure inserisci manualmente';
|
|
||||||
@override String get hostAddress => 'Indirizzo host';
|
@override String get hostAddress => 'Indirizzo host';
|
||||||
@override String get sessionId => 'ID sessione';
|
|
||||||
@override String get pin => 'PIN';
|
|
||||||
@override String get connected => 'Connesso';
|
@override String get connected => 'Connesso';
|
||||||
@override String get waitingForConnection => 'In attesa di connessione...';
|
@override String get serverRunning => 'Server remoto attivo';
|
||||||
|
@override String get serverStopped => 'Server remoto arrestato';
|
||||||
|
@override String get serverRunningDescription => 'I dispositivi mobili nella tua rete possono scoprire e connettersi a questa app';
|
||||||
|
@override String get serverStoppedDescription => 'Avvia il server per consentire ai dispositivi mobili di connettersi';
|
||||||
@override String get usePhoneToControl => 'Usa il tuo dispositivo mobile per controllare questa app';
|
@override String get usePhoneToControl => 'Usa il tuo dispositivo mobile per controllare questa app';
|
||||||
@override String copiedToClipboard({required Object label}) => '${label} copiato negli appunti';
|
@override String get startServer => 'Avvia server';
|
||||||
@override String get copyToClipboard => 'Copia negli appunti';
|
@override String get stopServer => 'Arresta server';
|
||||||
@override String get newSession => 'Nuova sessione';
|
|
||||||
@override String get minimize => 'Riduci';
|
@override String get minimize => 'Riduci';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingIt implements TranslationsCompanionRemo
|
|||||||
final TranslationsIt _root; // ignore: unused_field
|
final TranslationsIt _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get scan => 'Scansiona';
|
@override String get pairWithDesktop => 'Connetti al desktop';
|
||||||
@override String get manual => 'Manuale';
|
@override String get discoveryDescription => 'I dispositivi nella tua rete che eseguono Plezy con lo stesso account Plex appariranno automaticamente';
|
||||||
@override String get pairWithDesktop => 'Associa con desktop';
|
|
||||||
@override String get enterSessionDetails => 'Inserisci i dettagli della sessione mostrati sul tuo dispositivo desktop';
|
|
||||||
@override String get hostAddressHint => '192.168.1.100:48632';
|
@override String get hostAddressHint => '192.168.1.100:48632';
|
||||||
@override String get sessionIdHint => 'Inserisci ID sessione di 8 caratteri';
|
|
||||||
@override String get pinHint => 'Inserisci PIN di 6 cifre';
|
|
||||||
@override String get connecting => 'Connessione...';
|
@override String get connecting => 'Connessione...';
|
||||||
@override String get tips => 'Suggerimenti';
|
@override String get searchingForDevices => 'Ricerca dispositivi...';
|
||||||
@override String get tipDesktop => 'Apri Plezy sul tuo desktop e abilita Companion Remote dalle impostazioni o dal menu';
|
@override String get noDevicesFound => 'Nessun dispositivo trovato nella tua rete';
|
||||||
@override String get tipScan => 'Usa la scheda Scansiona per associare rapidamente scansionando il QR code sul tuo desktop';
|
@override String get noDevicesHint => 'Assicurati che Plezy sia aperto sul tuo desktop e che entrambi i dispositivi siano sulla stessa rete WiFi';
|
||||||
@override String get tipWifi => 'Assicurati che entrambi i dispositivi siano sulla stessa rete WiFi';
|
@override String get availableDevices => 'Dispositivi disponibili';
|
||||||
@override String get cameraPermissionRequired => 'L\'autorizzazione della fotocamera è necessaria per scansionare i QR code.\nConcedi l\'accesso alla fotocamera nelle impostazioni del dispositivo.';
|
@override String get manualConnection => 'Connessione manuale';
|
||||||
@override String cameraError({required Object error}) => 'Impossibile avviare la fotocamera: ${error}';
|
@override String get cryptoInitFailed => 'Impossibile inizializzare la connessione sicura. Assicurati di aver effettuato l\'accesso a un account Plex.';
|
||||||
@override String get scanInstruction => 'Punta la fotocamera verso il QR code mostrato sul tuo desktop';
|
|
||||||
@override String get invalidQrCode => 'Formato QR code non valido';
|
|
||||||
@override String get validationHostRequired => 'Inserisci l\'indirizzo host';
|
@override String get validationHostRequired => 'Inserisci l\'indirizzo host';
|
||||||
@override String get validationHostFormat => 'Il formato deve essere IP:porta (es. 192.168.1.100:48632)';
|
@override String get validationHostFormat => 'Il formato deve essere IP:porta (es. 192.168.1.100:48632)';
|
||||||
@override String get validationSessionIdRequired => 'Inserisci un ID sessione';
|
@override String get connectionTimedOut => 'Connessione scaduta. Assicurati che entrambi i dispositivi siano sulla stessa rete.';
|
||||||
@override String get validationSessionIdLength => 'L\'ID sessione deve essere di 8 caratteri';
|
@override String get sessionNotFound => 'Dispositivo non trovato. Assicurati che Plezy sia in esecuzione sull\'host.';
|
||||||
@override String get validationPinRequired => 'Inserisci un PIN';
|
@override String get authFailed => 'Autenticazione fallita. Assicurati che entrambi i dispositivi utilizzino lo stesso account Plex.';
|
||||||
@override String get validationPinLength => 'Il PIN deve essere di 6 cifre';
|
|
||||||
@override String get connectionTimedOut => 'Connessione scaduta. Verifica l\'ID sessione e il PIN.';
|
|
||||||
@override String get sessionNotFound => 'Sessione non trovata. Verifica le tue credenziali.';
|
|
||||||
@override String failedToConnect({required Object error}) => 'Connessione fallita: ${error}';
|
@override String failedToConnect({required Object error}) => 'Connessione fallita: ${error}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1600,6 +1590,8 @@ extension on TranslationsIt {
|
|||||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||||
'settings.seekAndTiming' => 'Seek & Timing',
|
'settings.seekAndTiming' => 'Seek & Timing',
|
||||||
'settings.behavior' => 'Behavior',
|
'settings.behavior' => 'Behavior',
|
||||||
|
'settings.companionRemoteServer' => 'Server telecomando',
|
||||||
|
'settings.companionRemoteServerDescription' => 'Consenti ai dispositivi mobili nella tua rete di controllare questa app',
|
||||||
'search.hint' => 'Cerca film. spettacoli, musica...',
|
'search.hint' => 'Cerca film. spettacoli, musica...',
|
||||||
'search.tryDifferentTerm' => 'Prova altri termini di ricerca',
|
'search.tryDifferentTerm' => 'Prova altri termini di ricerca',
|
||||||
'search.searchYourMedia' => 'Cerca nei tuoi media',
|
'search.searchYourMedia' => 'Cerca nei tuoi media',
|
||||||
@@ -1881,10 +1873,10 @@ extension on TranslationsIt {
|
|||||||
'libraries.groupings.seasons' => 'Stagioni',
|
'libraries.groupings.seasons' => 'Stagioni',
|
||||||
'libraries.groupings.episodes' => 'Episodi',
|
'libraries.groupings.episodes' => 'Episodi',
|
||||||
'libraries.groupings.folders' => 'Cartelle',
|
'libraries.groupings.folders' => 'Cartelle',
|
||||||
'about.title' => 'Informazioni',
|
|
||||||
'about.openSourceLicenses' => 'Licenze Open Source',
|
|
||||||
_ => null,
|
_ => null,
|
||||||
} ?? switch (path) {
|
} ?? switch (path) {
|
||||||
|
'about.title' => 'Informazioni',
|
||||||
|
'about.openSourceLicenses' => 'Licenze Open Source',
|
||||||
'about.versionLabel' => ({required Object version}) => 'Versione ${version}',
|
'about.versionLabel' => ({required Object version}) => 'Versione ${version}',
|
||||||
'about.appDescription' => 'Un bellissimo client Plex per Flutter',
|
'about.appDescription' => 'Un bellissimo client Plex per Flutter',
|
||||||
'about.viewLicensesDescription' => 'Visualizza le licenze delle librerie di terze parti',
|
'about.viewLicensesDescription' => 'Visualizza le licenze delle librerie di terze parti',
|
||||||
@@ -2076,45 +2068,33 @@ extension on TranslationsIt {
|
|||||||
'companionRemote.remoteControl' => 'Telecomando',
|
'companionRemote.remoteControl' => 'Telecomando',
|
||||||
'companionRemote.controlDesktop' => 'Controlla un dispositivo desktop',
|
'companionRemote.controlDesktop' => 'Controlla un dispositivo desktop',
|
||||||
'companionRemote.connectedTo' => ({required Object name}) => 'Connesso a ${name}',
|
'companionRemote.connectedTo' => ({required Object name}) => 'Connesso a ${name}',
|
||||||
'companionRemote.session.creatingSession' => 'Creazione sessione remota...',
|
'companionRemote.session.startingServer' => 'Avvio del server remoto...',
|
||||||
'companionRemote.session.failedToCreate' => 'Impossibile creare la sessione remota:',
|
'companionRemote.session.failedToCreate' => 'Impossibile avviare il server remoto:',
|
||||||
'companionRemote.session.noSession' => 'Nessuna sessione disponibile',
|
|
||||||
'companionRemote.session.scanQrCode' => 'Scansiona QR Code',
|
|
||||||
'companionRemote.session.orEnterManually' => 'Oppure inserisci manualmente',
|
|
||||||
'companionRemote.session.hostAddress' => 'Indirizzo host',
|
'companionRemote.session.hostAddress' => 'Indirizzo host',
|
||||||
'companionRemote.session.sessionId' => 'ID sessione',
|
|
||||||
'companionRemote.session.pin' => 'PIN',
|
|
||||||
'companionRemote.session.connected' => 'Connesso',
|
'companionRemote.session.connected' => 'Connesso',
|
||||||
'companionRemote.session.waitingForConnection' => 'In attesa di connessione...',
|
'companionRemote.session.serverRunning' => 'Server remoto attivo',
|
||||||
|
'companionRemote.session.serverStopped' => 'Server remoto arrestato',
|
||||||
|
'companionRemote.session.serverRunningDescription' => 'I dispositivi mobili nella tua rete possono scoprire e connettersi a questa app',
|
||||||
|
'companionRemote.session.serverStoppedDescription' => 'Avvia il server per consentire ai dispositivi mobili di connettersi',
|
||||||
'companionRemote.session.usePhoneToControl' => 'Usa il tuo dispositivo mobile per controllare questa app',
|
'companionRemote.session.usePhoneToControl' => 'Usa il tuo dispositivo mobile per controllare questa app',
|
||||||
'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} copiato negli appunti',
|
'companionRemote.session.startServer' => 'Avvia server',
|
||||||
'companionRemote.session.copyToClipboard' => 'Copia negli appunti',
|
'companionRemote.session.stopServer' => 'Arresta server',
|
||||||
'companionRemote.session.newSession' => 'Nuova sessione',
|
|
||||||
'companionRemote.session.minimize' => 'Riduci',
|
'companionRemote.session.minimize' => 'Riduci',
|
||||||
'companionRemote.pairing.scan' => 'Scansiona',
|
'companionRemote.pairing.pairWithDesktop' => 'Connetti al desktop',
|
||||||
'companionRemote.pairing.manual' => 'Manuale',
|
'companionRemote.pairing.discoveryDescription' => 'I dispositivi nella tua rete che eseguono Plezy con lo stesso account Plex appariranno automaticamente',
|
||||||
'companionRemote.pairing.pairWithDesktop' => 'Associa con desktop',
|
|
||||||
'companionRemote.pairing.enterSessionDetails' => 'Inserisci i dettagli della sessione mostrati sul tuo dispositivo desktop',
|
|
||||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||||
'companionRemote.pairing.sessionIdHint' => 'Inserisci ID sessione di 8 caratteri',
|
|
||||||
'companionRemote.pairing.pinHint' => 'Inserisci PIN di 6 cifre',
|
|
||||||
'companionRemote.pairing.connecting' => 'Connessione...',
|
'companionRemote.pairing.connecting' => 'Connessione...',
|
||||||
'companionRemote.pairing.tips' => 'Suggerimenti',
|
'companionRemote.pairing.searchingForDevices' => 'Ricerca dispositivi...',
|
||||||
'companionRemote.pairing.tipDesktop' => 'Apri Plezy sul tuo desktop e abilita Companion Remote dalle impostazioni o dal menu',
|
'companionRemote.pairing.noDevicesFound' => 'Nessun dispositivo trovato nella tua rete',
|
||||||
'companionRemote.pairing.tipScan' => 'Usa la scheda Scansiona per associare rapidamente scansionando il QR code sul tuo desktop',
|
'companionRemote.pairing.noDevicesHint' => 'Assicurati che Plezy sia aperto sul tuo desktop e che entrambi i dispositivi siano sulla stessa rete WiFi',
|
||||||
'companionRemote.pairing.tipWifi' => 'Assicurati che entrambi i dispositivi siano sulla stessa rete WiFi',
|
'companionRemote.pairing.availableDevices' => 'Dispositivi disponibili',
|
||||||
'companionRemote.pairing.cameraPermissionRequired' => 'L\'autorizzazione della fotocamera è necessaria per scansionare i QR code.\nConcedi l\'accesso alla fotocamera nelle impostazioni del dispositivo.',
|
'companionRemote.pairing.manualConnection' => 'Connessione manuale',
|
||||||
'companionRemote.pairing.cameraError' => ({required Object error}) => 'Impossibile avviare la fotocamera: ${error}',
|
'companionRemote.pairing.cryptoInitFailed' => 'Impossibile inizializzare la connessione sicura. Assicurati di aver effettuato l\'accesso a un account Plex.',
|
||||||
'companionRemote.pairing.scanInstruction' => 'Punta la fotocamera verso il QR code mostrato sul tuo desktop',
|
|
||||||
'companionRemote.pairing.invalidQrCode' => 'Formato QR code non valido',
|
|
||||||
'companionRemote.pairing.validationHostRequired' => 'Inserisci l\'indirizzo host',
|
'companionRemote.pairing.validationHostRequired' => 'Inserisci l\'indirizzo host',
|
||||||
'companionRemote.pairing.validationHostFormat' => 'Il formato deve essere IP:porta (es. 192.168.1.100:48632)',
|
'companionRemote.pairing.validationHostFormat' => 'Il formato deve essere IP:porta (es. 192.168.1.100:48632)',
|
||||||
'companionRemote.pairing.validationSessionIdRequired' => 'Inserisci un ID sessione',
|
'companionRemote.pairing.connectionTimedOut' => 'Connessione scaduta. Assicurati che entrambi i dispositivi siano sulla stessa rete.',
|
||||||
'companionRemote.pairing.validationSessionIdLength' => 'L\'ID sessione deve essere di 8 caratteri',
|
'companionRemote.pairing.sessionNotFound' => 'Dispositivo non trovato. Assicurati che Plezy sia in esecuzione sull\'host.',
|
||||||
'companionRemote.pairing.validationPinRequired' => 'Inserisci un PIN',
|
'companionRemote.pairing.authFailed' => 'Autenticazione fallita. Assicurati che entrambi i dispositivi utilizzino lo stesso account Plex.',
|
||||||
'companionRemote.pairing.validationPinLength' => 'Il PIN deve essere di 6 cifre',
|
|
||||||
'companionRemote.pairing.connectionTimedOut' => 'Connessione scaduta. Verifica l\'ID sessione e il PIN.',
|
|
||||||
'companionRemote.pairing.sessionNotFound' => 'Sessione non trovata. Verifica le tue credenziali.',
|
|
||||||
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Connessione fallita: ${error}',
|
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Connessione fallita: ${error}',
|
||||||
'companionRemote.remote.disconnectConfirm' => 'Vuoi disconnetterti dalla sessione remota?',
|
'companionRemote.remote.disconnectConfirm' => 'Vuoi disconnetterti dalla sessione remota?',
|
||||||
'companionRemote.remote.reconnecting' => 'Riconnessione...',
|
'companionRemote.remote.reconnecting' => 'Riconnessione...',
|
||||||
|
|||||||
+46
-66
@@ -358,6 +358,8 @@ class _TranslationsSettingsJa implements TranslationsSettingsEn {
|
|||||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||||
@override String get seekAndTiming => 'Seek & Timing';
|
@override String get seekAndTiming => 'Seek & Timing';
|
||||||
@override String get behavior => 'Behavior';
|
@override String get behavior => 'Behavior';
|
||||||
|
@override String get companionRemoteServer => 'コンパニオンリモートサーバー';
|
||||||
|
@override String get companionRemoteServerDescription => 'ネットワーク上のモバイルデバイスからこのアプリを操作できるようにする';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: search
|
// Path: search
|
||||||
@@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionJa implements TranslationsCompanionRemo
|
|||||||
final TranslationsJa _root; // ignore: unused_field
|
final TranslationsJa _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get creatingSession => 'リモートセッションを作成中...';
|
@override String get startingServer => 'リモートサーバーを起動中...';
|
||||||
@override String get failedToCreate => 'リモートセッションの作成に失敗しました:';
|
@override String get failedToCreate => 'リモートサーバーの起動に失敗しました:';
|
||||||
@override String get noSession => '利用可能なセッションがありません';
|
|
||||||
@override String get scanQrCode => 'QRコードをスキャン';
|
|
||||||
@override String get orEnterManually => 'または手動で入力';
|
|
||||||
@override String get hostAddress => 'ホストアドレス';
|
@override String get hostAddress => 'ホストアドレス';
|
||||||
@override String get sessionId => 'セッションID';
|
|
||||||
@override String get pin => 'PIN';
|
|
||||||
@override String get connected => '接続済み';
|
@override String get connected => '接続済み';
|
||||||
@override String get waitingForConnection => '接続を待機中...';
|
@override String get serverRunning => 'リモートサーバー稼働中';
|
||||||
@override String get usePhoneToControl => 'モバイルデバイスでこのアプリを操作';
|
@override String get serverStopped => 'リモートサーバー停止中';
|
||||||
@override String copiedToClipboard({required Object label}) => '${label}をクリップボードにコピーしました';
|
@override String get serverRunningDescription => 'ネットワーク上のモバイルデバイスがこのアプリを検出して接続できます';
|
||||||
@override String get copyToClipboard => 'クリップボードにコピー';
|
@override String get serverStoppedDescription => 'モバイルデバイスの接続を許可するにはサーバーを起動してください';
|
||||||
@override String get newSession => '新しいセッション';
|
@override String get usePhoneToControl => 'モバイルデバイスでこのアプリを操作できます';
|
||||||
|
@override String get startServer => 'サーバーを起動';
|
||||||
|
@override String get stopServer => 'サーバーを停止';
|
||||||
@override String get minimize => '最小化';
|
@override String get minimize => '最小化';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingJa implements TranslationsCompanionRemo
|
|||||||
final TranslationsJa _root; // ignore: unused_field
|
final TranslationsJa _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get scan => 'スキャン';
|
@override String get pairWithDesktop => 'デスクトップに接続';
|
||||||
@override String get manual => '手動';
|
@override String get discoveryDescription => '同じPlexアカウントでPlezyを実行しているネットワーク上のデバイスが自動的に表示されます';
|
||||||
@override String get pairWithDesktop => 'デスクトップとペアリング';
|
|
||||||
@override String get enterSessionDetails => 'デスクトップデバイスに表示されたセッション情報を入力';
|
|
||||||
@override String get hostAddressHint => '192.168.1.100:48632';
|
@override String get hostAddressHint => '192.168.1.100:48632';
|
||||||
@override String get sessionIdHint => '8文字のセッションIDを入力';
|
|
||||||
@override String get pinHint => '6桁のPINを入力';
|
|
||||||
@override String get connecting => '接続中...';
|
@override String get connecting => '接続中...';
|
||||||
@override String get tips => 'ヒント';
|
@override String get searchingForDevices => 'デバイスを検索中...';
|
||||||
@override String get tipDesktop => 'デスクトップでPlezyを開き、設定またはメニューからコンパニオンリモートを有効にしてください';
|
@override String get noDevicesFound => 'ネットワーク上にデバイスが見つかりません';
|
||||||
@override String get tipScan => 'スキャンタブを使用して、デスクトップのQRコードをスキャンして素早くペアリング';
|
@override String get noDevicesHint => 'デスクトップでPlezyが開いていること、両方のデバイスが同じWiFiネットワーク上にあることを確認してください';
|
||||||
@override String get tipWifi => '両方のデバイスが同じWiFiネットワークに接続されていることを確認';
|
@override String get availableDevices => '利用可能なデバイス';
|
||||||
@override String get cameraPermissionRequired => 'QRコードをスキャンするにはカメラの権限が必要です。\nデバイスの設定でカメラへのアクセスを許可してください。';
|
@override String get manualConnection => '手動接続';
|
||||||
@override String cameraError({required Object error}) => 'カメラを起動できませんでした: ${error}';
|
@override String get cryptoInitFailed => '安全な接続を初期化できませんでした。Plexアカウントにサインインしていることを確認してください。';
|
||||||
@override String get scanInstruction => 'デスクトップに表示されたQRコードにカメラを向けてください';
|
|
||||||
@override String get invalidQrCode => '無効なQRコード形式';
|
|
||||||
@override String get validationHostRequired => 'ホストアドレスを入力してください';
|
@override String get validationHostRequired => 'ホストアドレスを入力してください';
|
||||||
@override String get validationHostFormat => '形式はIP:ポートである必要があります(例: 192.168.1.100:48632)';
|
@override String get validationHostFormat => '形式はIP:ポートである必要があります(例: 192.168.1.100:48632)';
|
||||||
@override String get validationSessionIdRequired => 'セッションIDを入力してください';
|
@override String get connectionTimedOut => '接続がタイムアウトしました。両方のデバイスが同じネットワーク上にあることを確認してください。';
|
||||||
@override String get validationSessionIdLength => 'セッションIDは8文字である必要があります';
|
@override String get sessionNotFound => 'デバイスが見つかりません。ホストでPlezyが実行されていることを確認してください。';
|
||||||
@override String get validationPinRequired => 'PINを入力してください';
|
@override String get authFailed => '認証に失敗しました。両方のデバイスが同じPlexアカウントを使用していることを確認してください。';
|
||||||
@override String get validationPinLength => 'PINは6桁である必要があります';
|
|
||||||
@override String get connectionTimedOut => '接続がタイムアウトしました。セッションIDとPINを確認してください。';
|
|
||||||
@override String get sessionNotFound => 'セッションが見つかりませんでした。認証情報を確認してください。';
|
|
||||||
@override String failedToConnect({required Object error}) => '接続に失敗しました: ${error}';
|
@override String failedToConnect({required Object error}) => '接続に失敗しました: ${error}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1600,6 +1590,8 @@ extension on TranslationsJa {
|
|||||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||||
'settings.seekAndTiming' => 'Seek & Timing',
|
'settings.seekAndTiming' => 'Seek & Timing',
|
||||||
'settings.behavior' => 'Behavior',
|
'settings.behavior' => 'Behavior',
|
||||||
|
'settings.companionRemoteServer' => 'コンパニオンリモートサーバー',
|
||||||
|
'settings.companionRemoteServerDescription' => 'ネットワーク上のモバイルデバイスからこのアプリを操作できるようにする',
|
||||||
'search.hint' => '映画、番組、音楽を検索...',
|
'search.hint' => '映画、番組、音楽を検索...',
|
||||||
'search.tryDifferentTerm' => '別の検索語をお試しください',
|
'search.tryDifferentTerm' => '別の検索語をお試しください',
|
||||||
'search.searchYourMedia' => 'メディアを検索',
|
'search.searchYourMedia' => 'メディアを検索',
|
||||||
@@ -1881,10 +1873,10 @@ extension on TranslationsJa {
|
|||||||
'libraries.groupings.seasons' => 'シーズン',
|
'libraries.groupings.seasons' => 'シーズン',
|
||||||
'libraries.groupings.episodes' => 'エピソード',
|
'libraries.groupings.episodes' => 'エピソード',
|
||||||
'libraries.groupings.folders' => 'フォルダ',
|
'libraries.groupings.folders' => 'フォルダ',
|
||||||
'about.title' => 'アプリについて',
|
|
||||||
'about.openSourceLicenses' => 'オープンソースライセンス',
|
|
||||||
_ => null,
|
_ => null,
|
||||||
} ?? switch (path) {
|
} ?? switch (path) {
|
||||||
|
'about.title' => 'アプリについて',
|
||||||
|
'about.openSourceLicenses' => 'オープンソースライセンス',
|
||||||
'about.versionLabel' => ({required Object version}) => 'バージョン ${version}',
|
'about.versionLabel' => ({required Object version}) => 'バージョン ${version}',
|
||||||
'about.appDescription' => 'Flutter製の美しいPlexクライアント',
|
'about.appDescription' => 'Flutter製の美しいPlexクライアント',
|
||||||
'about.viewLicensesDescription' => 'サードパーティライブラリのライセンスを表示',
|
'about.viewLicensesDescription' => 'サードパーティライブラリのライセンスを表示',
|
||||||
@@ -2076,45 +2068,33 @@ extension on TranslationsJa {
|
|||||||
'companionRemote.remoteControl' => 'リモコン',
|
'companionRemote.remoteControl' => 'リモコン',
|
||||||
'companionRemote.controlDesktop' => 'デスクトップデバイスを操作',
|
'companionRemote.controlDesktop' => 'デスクトップデバイスを操作',
|
||||||
'companionRemote.connectedTo' => ({required Object name}) => '${name}に接続中',
|
'companionRemote.connectedTo' => ({required Object name}) => '${name}に接続中',
|
||||||
'companionRemote.session.creatingSession' => 'リモートセッションを作成中...',
|
'companionRemote.session.startingServer' => 'リモートサーバーを起動中...',
|
||||||
'companionRemote.session.failedToCreate' => 'リモートセッションの作成に失敗しました:',
|
'companionRemote.session.failedToCreate' => 'リモートサーバーの起動に失敗しました:',
|
||||||
'companionRemote.session.noSession' => '利用可能なセッションがありません',
|
|
||||||
'companionRemote.session.scanQrCode' => 'QRコードをスキャン',
|
|
||||||
'companionRemote.session.orEnterManually' => 'または手動で入力',
|
|
||||||
'companionRemote.session.hostAddress' => 'ホストアドレス',
|
'companionRemote.session.hostAddress' => 'ホストアドレス',
|
||||||
'companionRemote.session.sessionId' => 'セッションID',
|
|
||||||
'companionRemote.session.pin' => 'PIN',
|
|
||||||
'companionRemote.session.connected' => '接続済み',
|
'companionRemote.session.connected' => '接続済み',
|
||||||
'companionRemote.session.waitingForConnection' => '接続を待機中...',
|
'companionRemote.session.serverRunning' => 'リモートサーバー稼働中',
|
||||||
'companionRemote.session.usePhoneToControl' => 'モバイルデバイスでこのアプリを操作',
|
'companionRemote.session.serverStopped' => 'リモートサーバー停止中',
|
||||||
'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label}をクリップボードにコピーしました',
|
'companionRemote.session.serverRunningDescription' => 'ネットワーク上のモバイルデバイスがこのアプリを検出して接続できます',
|
||||||
'companionRemote.session.copyToClipboard' => 'クリップボードにコピー',
|
'companionRemote.session.serverStoppedDescription' => 'モバイルデバイスの接続を許可するにはサーバーを起動してください',
|
||||||
'companionRemote.session.newSession' => '新しいセッション',
|
'companionRemote.session.usePhoneToControl' => 'モバイルデバイスでこのアプリを操作できます',
|
||||||
|
'companionRemote.session.startServer' => 'サーバーを起動',
|
||||||
|
'companionRemote.session.stopServer' => 'サーバーを停止',
|
||||||
'companionRemote.session.minimize' => '最小化',
|
'companionRemote.session.minimize' => '最小化',
|
||||||
'companionRemote.pairing.scan' => 'スキャン',
|
'companionRemote.pairing.pairWithDesktop' => 'デスクトップに接続',
|
||||||
'companionRemote.pairing.manual' => '手動',
|
'companionRemote.pairing.discoveryDescription' => '同じPlexアカウントでPlezyを実行しているネットワーク上のデバイスが自動的に表示されます',
|
||||||
'companionRemote.pairing.pairWithDesktop' => 'デスクトップとペアリング',
|
|
||||||
'companionRemote.pairing.enterSessionDetails' => 'デスクトップデバイスに表示されたセッション情報を入力',
|
|
||||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||||
'companionRemote.pairing.sessionIdHint' => '8文字のセッションIDを入力',
|
|
||||||
'companionRemote.pairing.pinHint' => '6桁のPINを入力',
|
|
||||||
'companionRemote.pairing.connecting' => '接続中...',
|
'companionRemote.pairing.connecting' => '接続中...',
|
||||||
'companionRemote.pairing.tips' => 'ヒント',
|
'companionRemote.pairing.searchingForDevices' => 'デバイスを検索中...',
|
||||||
'companionRemote.pairing.tipDesktop' => 'デスクトップでPlezyを開き、設定またはメニューからコンパニオンリモートを有効にしてください',
|
'companionRemote.pairing.noDevicesFound' => 'ネットワーク上にデバイスが見つかりません',
|
||||||
'companionRemote.pairing.tipScan' => 'スキャンタブを使用して、デスクトップのQRコードをスキャンして素早くペアリング',
|
'companionRemote.pairing.noDevicesHint' => 'デスクトップでPlezyが開いていること、両方のデバイスが同じWiFiネットワーク上にあることを確認してください',
|
||||||
'companionRemote.pairing.tipWifi' => '両方のデバイスが同じWiFiネットワークに接続されていることを確認',
|
'companionRemote.pairing.availableDevices' => '利用可能なデバイス',
|
||||||
'companionRemote.pairing.cameraPermissionRequired' => 'QRコードをスキャンするにはカメラの権限が必要です。\nデバイスの設定でカメラへのアクセスを許可してください。',
|
'companionRemote.pairing.manualConnection' => '手動接続',
|
||||||
'companionRemote.pairing.cameraError' => ({required Object error}) => 'カメラを起動できませんでした: ${error}',
|
'companionRemote.pairing.cryptoInitFailed' => '安全な接続を初期化できませんでした。Plexアカウントにサインインしていることを確認してください。',
|
||||||
'companionRemote.pairing.scanInstruction' => 'デスクトップに表示されたQRコードにカメラを向けてください',
|
|
||||||
'companionRemote.pairing.invalidQrCode' => '無効なQRコード形式',
|
|
||||||
'companionRemote.pairing.validationHostRequired' => 'ホストアドレスを入力してください',
|
'companionRemote.pairing.validationHostRequired' => 'ホストアドレスを入力してください',
|
||||||
'companionRemote.pairing.validationHostFormat' => '形式はIP:ポートである必要があります(例: 192.168.1.100:48632)',
|
'companionRemote.pairing.validationHostFormat' => '形式はIP:ポートである必要があります(例: 192.168.1.100:48632)',
|
||||||
'companionRemote.pairing.validationSessionIdRequired' => 'セッションIDを入力してください',
|
'companionRemote.pairing.connectionTimedOut' => '接続がタイムアウトしました。両方のデバイスが同じネットワーク上にあることを確認してください。',
|
||||||
'companionRemote.pairing.validationSessionIdLength' => 'セッションIDは8文字である必要があります',
|
'companionRemote.pairing.sessionNotFound' => 'デバイスが見つかりません。ホストでPlezyが実行されていることを確認してください。',
|
||||||
'companionRemote.pairing.validationPinRequired' => 'PINを入力してください',
|
'companionRemote.pairing.authFailed' => '認証に失敗しました。両方のデバイスが同じPlexアカウントを使用していることを確認してください。',
|
||||||
'companionRemote.pairing.validationPinLength' => 'PINは6桁である必要があります',
|
|
||||||
'companionRemote.pairing.connectionTimedOut' => '接続がタイムアウトしました。セッションIDとPINを確認してください。',
|
|
||||||
'companionRemote.pairing.sessionNotFound' => 'セッションが見つかりませんでした。認証情報を確認してください。',
|
|
||||||
'companionRemote.pairing.failedToConnect' => ({required Object error}) => '接続に失敗しました: ${error}',
|
'companionRemote.pairing.failedToConnect' => ({required Object error}) => '接続に失敗しました: ${error}',
|
||||||
'companionRemote.remote.disconnectConfirm' => 'リモートセッションから切断しますか?',
|
'companionRemote.remote.disconnectConfirm' => 'リモートセッションから切断しますか?',
|
||||||
'companionRemote.remote.reconnecting' => '再接続中...',
|
'companionRemote.remote.reconnecting' => '再接続中...',
|
||||||
|
|||||||
+46
-66
@@ -358,6 +358,8 @@ class _TranslationsSettingsKo implements TranslationsSettingsEn {
|
|||||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||||
@override String get seekAndTiming => 'Seek & Timing';
|
@override String get seekAndTiming => 'Seek & Timing';
|
||||||
@override String get behavior => 'Behavior';
|
@override String get behavior => 'Behavior';
|
||||||
|
@override String get companionRemoteServer => '컴패니언 리모트 서버';
|
||||||
|
@override String get companionRemoteServerDescription => '네트워크의 모바일 기기가 이 앱을 제어할 수 있도록 허용';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: search
|
// Path: search
|
||||||
@@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionKo implements TranslationsCompanionRemo
|
|||||||
final TranslationsKo _root; // ignore: unused_field
|
final TranslationsKo _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get creatingSession => '원격 세션 생성 중...';
|
@override String get startingServer => '원격 서버 시작 중...';
|
||||||
@override String get failedToCreate => '원격 세션 생성 실패:';
|
@override String get failedToCreate => '원격 서버를 시작하지 못했습니다:';
|
||||||
@override String get noSession => '사용 가능한 세션이 없습니다';
|
|
||||||
@override String get scanQrCode => 'QR 코드 스캔';
|
|
||||||
@override String get orEnterManually => '또는 수동으로 입력';
|
|
||||||
@override String get hostAddress => '호스트 주소';
|
@override String get hostAddress => '호스트 주소';
|
||||||
@override String get sessionId => '세션 ID';
|
|
||||||
@override String get pin => 'PIN';
|
|
||||||
@override String get connected => '연결됨';
|
@override String get connected => '연결됨';
|
||||||
@override String get waitingForConnection => '연결 대기 중...';
|
@override String get serverRunning => '원격 서버 활성';
|
||||||
|
@override String get serverStopped => '원격 서버 중지됨';
|
||||||
|
@override String get serverRunningDescription => '네트워크의 모바일 기기가 이 앱을 검색하고 연결할 수 있습니다';
|
||||||
|
@override String get serverStoppedDescription => '모바일 기기의 연결을 허용하려면 서버를 시작하세요';
|
||||||
@override String get usePhoneToControl => '모바일 기기로 이 앱을 제어하세요';
|
@override String get usePhoneToControl => '모바일 기기로 이 앱을 제어하세요';
|
||||||
@override String copiedToClipboard({required Object label}) => '${label}이(가) 클립보드에 복사되었습니다';
|
@override String get startServer => '서버 시작';
|
||||||
@override String get copyToClipboard => '클립보드에 복사';
|
@override String get stopServer => '서버 중지';
|
||||||
@override String get newSession => '새 세션';
|
|
||||||
@override String get minimize => '최소화';
|
@override String get minimize => '최소화';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingKo implements TranslationsCompanionRemo
|
|||||||
final TranslationsKo _root; // ignore: unused_field
|
final TranslationsKo _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get scan => '스캔';
|
@override String get pairWithDesktop => '데스크톱에 연결';
|
||||||
@override String get manual => '수동';
|
@override String get discoveryDescription => '같은 Plex 계정으로 Plezy를 실행 중인 네트워크의 기기가 자동으로 표시됩니다';
|
||||||
@override String get pairWithDesktop => '데스크톱과 페어링';
|
|
||||||
@override String get enterSessionDetails => '데스크톱 기기에 표시된 세션 정보를 입력하세요';
|
|
||||||
@override String get hostAddressHint => '192.168.1.100:48632';
|
@override String get hostAddressHint => '192.168.1.100:48632';
|
||||||
@override String get sessionIdHint => '8자리 세션 ID 입력';
|
|
||||||
@override String get pinHint => '6자리 PIN 입력';
|
|
||||||
@override String get connecting => '연결 중...';
|
@override String get connecting => '연결 중...';
|
||||||
@override String get tips => '팁';
|
@override String get searchingForDevices => '기기 검색 중...';
|
||||||
@override String get tipDesktop => '데스크톱에서 Plezy를 열고 설정 또는 메뉴에서 Companion Remote를 활성화하세요';
|
@override String get noDevicesFound => '네트워크에서 기기를 찾을 수 없습니다';
|
||||||
@override String get tipScan => '스캔 탭을 사용하여 데스크톱의 QR 코드를 스캔하면 빠르게 페어링할 수 있습니다';
|
@override String get noDevicesHint => '데스크톱에서 Plezy가 열려 있고 두 기기가 같은 WiFi 네트워크에 있는지 확인하세요';
|
||||||
@override String get tipWifi => '두 기기가 동일한 WiFi 네트워크에 연결되어 있는지 확인하세요';
|
@override String get availableDevices => '사용 가능한 기기';
|
||||||
@override String get cameraPermissionRequired => 'QR 코드를 스캔하려면 카메라 권한이 필요합니다.\n기기 설정에서 카메라 접근을 허용해 주세요.';
|
@override String get manualConnection => '수동 연결';
|
||||||
@override String cameraError({required Object error}) => '카메라를 시작할 수 없습니다: ${error}';
|
@override String get cryptoInitFailed => '보안 연결을 초기화할 수 없습니다. Plex 계정에 로그인되어 있는지 확인하세요.';
|
||||||
@override String get scanInstruction => '데스크톱에 표시된 QR 코드에 카메라를 향하세요';
|
|
||||||
@override String get invalidQrCode => '유효하지 않은 QR 코드 형식';
|
|
||||||
@override String get validationHostRequired => '호스트 주소를 입력하세요';
|
@override String get validationHostRequired => '호스트 주소를 입력하세요';
|
||||||
@override String get validationHostFormat => 'IP:포트 형식이어야 합니다 (예: 192.168.1.100:48632)';
|
@override String get validationHostFormat => '형식은 IP:포트여야 합니다 (예: 192.168.1.100:48632)';
|
||||||
@override String get validationSessionIdRequired => '세션 ID를 입력하세요';
|
@override String get connectionTimedOut => '연결 시간이 초과되었습니다. 두 기기가 같은 네트워크에 있는지 확인하세요.';
|
||||||
@override String get validationSessionIdLength => '세션 ID는 8자리여야 합니다';
|
@override String get sessionNotFound => '기기를 찾을 수 없습니다. 호스트에서 Plezy가 실행 중인지 확인하세요.';
|
||||||
@override String get validationPinRequired => 'PIN을 입력하세요';
|
@override String get authFailed => '인증에 실패했습니다. 두 기기가 같은 Plex 계정을 사용하는지 확인하세요.';
|
||||||
@override String get validationPinLength => 'PIN은 6자리여야 합니다';
|
|
||||||
@override String get connectionTimedOut => '연결 시간이 초과되었습니다. 세션 ID와 PIN을 확인하세요.';
|
|
||||||
@override String get sessionNotFound => '세션을 찾을 수 없습니다. 자격 증명을 확인하세요.';
|
|
||||||
@override String failedToConnect({required Object error}) => '연결 실패: ${error}';
|
@override String failedToConnect({required Object error}) => '연결 실패: ${error}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1600,6 +1590,8 @@ extension on TranslationsKo {
|
|||||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||||
'settings.seekAndTiming' => 'Seek & Timing',
|
'settings.seekAndTiming' => 'Seek & Timing',
|
||||||
'settings.behavior' => 'Behavior',
|
'settings.behavior' => 'Behavior',
|
||||||
|
'settings.companionRemoteServer' => '컴패니언 리모트 서버',
|
||||||
|
'settings.companionRemoteServerDescription' => '네트워크의 모바일 기기가 이 앱을 제어할 수 있도록 허용',
|
||||||
'search.hint' => '영화, 시리즈, 음악 등을 검색하세요...',
|
'search.hint' => '영화, 시리즈, 음악 등을 검색하세요...',
|
||||||
'search.tryDifferentTerm' => '다른 검색어를 시도해 보세요',
|
'search.tryDifferentTerm' => '다른 검색어를 시도해 보세요',
|
||||||
'search.searchYourMedia' => '미디어 검색',
|
'search.searchYourMedia' => '미디어 검색',
|
||||||
@@ -1881,10 +1873,10 @@ extension on TranslationsKo {
|
|||||||
'libraries.groupings.seasons' => '시즌',
|
'libraries.groupings.seasons' => '시즌',
|
||||||
'libraries.groupings.episodes' => '화',
|
'libraries.groupings.episodes' => '화',
|
||||||
'libraries.groupings.folders' => '폴더',
|
'libraries.groupings.folders' => '폴더',
|
||||||
'about.title' => '소개',
|
|
||||||
'about.openSourceLicenses' => '오픈소스 라이선스',
|
|
||||||
_ => null,
|
_ => null,
|
||||||
} ?? switch (path) {
|
} ?? switch (path) {
|
||||||
|
'about.title' => '소개',
|
||||||
|
'about.openSourceLicenses' => '오픈소스 라이선스',
|
||||||
'about.versionLabel' => ({required Object version}) => '버전 ${version}',
|
'about.versionLabel' => ({required Object version}) => '버전 ${version}',
|
||||||
'about.appDescription' => '아름다운 Flutter Plex 클라이언트',
|
'about.appDescription' => '아름다운 Flutter Plex 클라이언트',
|
||||||
'about.viewLicensesDescription' => '타사 라이브러리 라이선스 보기',
|
'about.viewLicensesDescription' => '타사 라이브러리 라이선스 보기',
|
||||||
@@ -2076,45 +2068,33 @@ extension on TranslationsKo {
|
|||||||
'companionRemote.remoteControl' => '원격 제어',
|
'companionRemote.remoteControl' => '원격 제어',
|
||||||
'companionRemote.controlDesktop' => '데스크톱 기기 제어',
|
'companionRemote.controlDesktop' => '데스크톱 기기 제어',
|
||||||
'companionRemote.connectedTo' => ({required Object name}) => '${name}에 연결됨',
|
'companionRemote.connectedTo' => ({required Object name}) => '${name}에 연결됨',
|
||||||
'companionRemote.session.creatingSession' => '원격 세션 생성 중...',
|
'companionRemote.session.startingServer' => '원격 서버 시작 중...',
|
||||||
'companionRemote.session.failedToCreate' => '원격 세션 생성 실패:',
|
'companionRemote.session.failedToCreate' => '원격 서버를 시작하지 못했습니다:',
|
||||||
'companionRemote.session.noSession' => '사용 가능한 세션이 없습니다',
|
|
||||||
'companionRemote.session.scanQrCode' => 'QR 코드 스캔',
|
|
||||||
'companionRemote.session.orEnterManually' => '또는 수동으로 입력',
|
|
||||||
'companionRemote.session.hostAddress' => '호스트 주소',
|
'companionRemote.session.hostAddress' => '호스트 주소',
|
||||||
'companionRemote.session.sessionId' => '세션 ID',
|
|
||||||
'companionRemote.session.pin' => 'PIN',
|
|
||||||
'companionRemote.session.connected' => '연결됨',
|
'companionRemote.session.connected' => '연결됨',
|
||||||
'companionRemote.session.waitingForConnection' => '연결 대기 중...',
|
'companionRemote.session.serverRunning' => '원격 서버 활성',
|
||||||
|
'companionRemote.session.serverStopped' => '원격 서버 중지됨',
|
||||||
|
'companionRemote.session.serverRunningDescription' => '네트워크의 모바일 기기가 이 앱을 검색하고 연결할 수 있습니다',
|
||||||
|
'companionRemote.session.serverStoppedDescription' => '모바일 기기의 연결을 허용하려면 서버를 시작하세요',
|
||||||
'companionRemote.session.usePhoneToControl' => '모바일 기기로 이 앱을 제어하세요',
|
'companionRemote.session.usePhoneToControl' => '모바일 기기로 이 앱을 제어하세요',
|
||||||
'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label}이(가) 클립보드에 복사되었습니다',
|
'companionRemote.session.startServer' => '서버 시작',
|
||||||
'companionRemote.session.copyToClipboard' => '클립보드에 복사',
|
'companionRemote.session.stopServer' => '서버 중지',
|
||||||
'companionRemote.session.newSession' => '새 세션',
|
|
||||||
'companionRemote.session.minimize' => '최소화',
|
'companionRemote.session.minimize' => '최소화',
|
||||||
'companionRemote.pairing.scan' => '스캔',
|
'companionRemote.pairing.pairWithDesktop' => '데스크톱에 연결',
|
||||||
'companionRemote.pairing.manual' => '수동',
|
'companionRemote.pairing.discoveryDescription' => '같은 Plex 계정으로 Plezy를 실행 중인 네트워크의 기기가 자동으로 표시됩니다',
|
||||||
'companionRemote.pairing.pairWithDesktop' => '데스크톱과 페어링',
|
|
||||||
'companionRemote.pairing.enterSessionDetails' => '데스크톱 기기에 표시된 세션 정보를 입력하세요',
|
|
||||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||||
'companionRemote.pairing.sessionIdHint' => '8자리 세션 ID 입력',
|
|
||||||
'companionRemote.pairing.pinHint' => '6자리 PIN 입력',
|
|
||||||
'companionRemote.pairing.connecting' => '연결 중...',
|
'companionRemote.pairing.connecting' => '연결 중...',
|
||||||
'companionRemote.pairing.tips' => '팁',
|
'companionRemote.pairing.searchingForDevices' => '기기 검색 중...',
|
||||||
'companionRemote.pairing.tipDesktop' => '데스크톱에서 Plezy를 열고 설정 또는 메뉴에서 Companion Remote를 활성화하세요',
|
'companionRemote.pairing.noDevicesFound' => '네트워크에서 기기를 찾을 수 없습니다',
|
||||||
'companionRemote.pairing.tipScan' => '스캔 탭을 사용하여 데스크톱의 QR 코드를 스캔하면 빠르게 페어링할 수 있습니다',
|
'companionRemote.pairing.noDevicesHint' => '데스크톱에서 Plezy가 열려 있고 두 기기가 같은 WiFi 네트워크에 있는지 확인하세요',
|
||||||
'companionRemote.pairing.tipWifi' => '두 기기가 동일한 WiFi 네트워크에 연결되어 있는지 확인하세요',
|
'companionRemote.pairing.availableDevices' => '사용 가능한 기기',
|
||||||
'companionRemote.pairing.cameraPermissionRequired' => 'QR 코드를 스캔하려면 카메라 권한이 필요합니다.\n기기 설정에서 카메라 접근을 허용해 주세요.',
|
'companionRemote.pairing.manualConnection' => '수동 연결',
|
||||||
'companionRemote.pairing.cameraError' => ({required Object error}) => '카메라를 시작할 수 없습니다: ${error}',
|
'companionRemote.pairing.cryptoInitFailed' => '보안 연결을 초기화할 수 없습니다. Plex 계정에 로그인되어 있는지 확인하세요.',
|
||||||
'companionRemote.pairing.scanInstruction' => '데스크톱에 표시된 QR 코드에 카메라를 향하세요',
|
|
||||||
'companionRemote.pairing.invalidQrCode' => '유효하지 않은 QR 코드 형식',
|
|
||||||
'companionRemote.pairing.validationHostRequired' => '호스트 주소를 입력하세요',
|
'companionRemote.pairing.validationHostRequired' => '호스트 주소를 입력하세요',
|
||||||
'companionRemote.pairing.validationHostFormat' => 'IP:포트 형식이어야 합니다 (예: 192.168.1.100:48632)',
|
'companionRemote.pairing.validationHostFormat' => '형식은 IP:포트여야 합니다 (예: 192.168.1.100:48632)',
|
||||||
'companionRemote.pairing.validationSessionIdRequired' => '세션 ID를 입력하세요',
|
'companionRemote.pairing.connectionTimedOut' => '연결 시간이 초과되었습니다. 두 기기가 같은 네트워크에 있는지 확인하세요.',
|
||||||
'companionRemote.pairing.validationSessionIdLength' => '세션 ID는 8자리여야 합니다',
|
'companionRemote.pairing.sessionNotFound' => '기기를 찾을 수 없습니다. 호스트에서 Plezy가 실행 중인지 확인하세요.',
|
||||||
'companionRemote.pairing.validationPinRequired' => 'PIN을 입력하세요',
|
'companionRemote.pairing.authFailed' => '인증에 실패했습니다. 두 기기가 같은 Plex 계정을 사용하는지 확인하세요.',
|
||||||
'companionRemote.pairing.validationPinLength' => 'PIN은 6자리여야 합니다',
|
|
||||||
'companionRemote.pairing.connectionTimedOut' => '연결 시간이 초과되었습니다. 세션 ID와 PIN을 확인하세요.',
|
|
||||||
'companionRemote.pairing.sessionNotFound' => '세션을 찾을 수 없습니다. 자격 증명을 확인하세요.',
|
|
||||||
'companionRemote.pairing.failedToConnect' => ({required Object error}) => '연결 실패: ${error}',
|
'companionRemote.pairing.failedToConnect' => ({required Object error}) => '연결 실패: ${error}',
|
||||||
'companionRemote.remote.disconnectConfirm' => '원격 세션 연결을 해제하시겠습니까?',
|
'companionRemote.remote.disconnectConfirm' => '원격 세션 연결을 해제하시겠습니까?',
|
||||||
'companionRemote.remote.reconnecting' => '재연결 중...',
|
'companionRemote.remote.reconnecting' => '재연결 중...',
|
||||||
|
|||||||
+50
-70
@@ -358,6 +358,8 @@ class _TranslationsSettingsNb implements TranslationsSettingsEn {
|
|||||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||||
@override String get seekAndTiming => 'Seek & Timing';
|
@override String get seekAndTiming => 'Seek & Timing';
|
||||||
@override String get behavior => 'Behavior';
|
@override String get behavior => 'Behavior';
|
||||||
|
@override String get companionRemoteServer => 'Companion Remote-server';
|
||||||
|
@override String get companionRemoteServerDescription => 'Tillat mobilenheter på nettverket ditt å styre denne appen';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: search
|
// Path: search
|
||||||
@@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionNb implements TranslationsCompanionRemo
|
|||||||
final TranslationsNb _root; // ignore: unused_field
|
final TranslationsNb _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get creatingSession => 'Oppretter fjernøkt...';
|
@override String get startingServer => 'Starter fjernserver...';
|
||||||
@override String get failedToCreate => 'Kunne ikke opprette fjernøkt:';
|
@override String get failedToCreate => 'Kunne ikke starte fjernserver:';
|
||||||
@override String get noSession => 'Ingen økt tilgjengelig';
|
|
||||||
@override String get scanQrCode => 'Skann QR-kode';
|
|
||||||
@override String get orEnterManually => 'Eller skriv inn manuelt';
|
|
||||||
@override String get hostAddress => 'Vertsadresse';
|
@override String get hostAddress => 'Vertsadresse';
|
||||||
@override String get sessionId => 'Økt-ID';
|
|
||||||
@override String get pin => 'PIN';
|
|
||||||
@override String get connected => 'Tilkoblet';
|
@override String get connected => 'Tilkoblet';
|
||||||
@override String get waitingForConnection => 'Venter på tilkobling...';
|
@override String get serverRunning => 'Fjernserver aktiv';
|
||||||
@override String get usePhoneToControl => 'Bruk mobilenheten din til å kontrollere denne appen';
|
@override String get serverStopped => 'Fjernserver stoppet';
|
||||||
@override String copiedToClipboard({required Object label}) => '${label} kopiert til utklippstavle';
|
@override String get serverRunningDescription => 'Mobilenheter på nettverket ditt kan oppdage og koble til denne appen';
|
||||||
@override String get copyToClipboard => 'Kopier til utklippstavle';
|
@override String get serverStoppedDescription => 'Start serveren for å la mobilenheter koble til';
|
||||||
@override String get newSession => 'Ny økt';
|
@override String get usePhoneToControl => 'Bruk mobilenheten din til å styre denne appen';
|
||||||
|
@override String get startServer => 'Start server';
|
||||||
|
@override String get stopServer => 'Stopp server';
|
||||||
@override String get minimize => 'Minimer';
|
@override String get minimize => 'Minimer';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingNb implements TranslationsCompanionRemo
|
|||||||
final TranslationsNb _root; // ignore: unused_field
|
final TranslationsNb _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get scan => 'Skann';
|
@override String get pairWithDesktop => 'Koble til datamaskin';
|
||||||
@override String get manual => 'Manuell';
|
@override String get discoveryDescription => 'Enheter på nettverket ditt som kjører Plezy med samme Plex-konto vil vises automatisk';
|
||||||
@override String get pairWithDesktop => 'Koble til stasjonær';
|
|
||||||
@override String get enterSessionDetails => 'Skriv inn øktdetaljene som vises på den stasjonære enheten din';
|
|
||||||
@override String get hostAddressHint => '192.168.1.100:48632';
|
@override String get hostAddressHint => '192.168.1.100:48632';
|
||||||
@override String get sessionIdHint => 'Skriv inn 8-tegns økt-ID';
|
|
||||||
@override String get pinHint => 'Skriv inn 6-sifret PIN';
|
|
||||||
@override String get connecting => 'Kobler til...';
|
@override String get connecting => 'Kobler til...';
|
||||||
@override String get tips => 'Tips';
|
@override String get searchingForDevices => 'Søker etter enheter...';
|
||||||
@override String get tipDesktop => 'Åpne Plezy på datamaskinen din og aktiver Følgesvenn-fjernkontroll fra innstillinger eller meny';
|
@override String get noDevicesFound => 'Ingen enheter funnet på nettverket ditt';
|
||||||
@override String get tipScan => 'Bruk Skann-fanen for å raskt koble til ved å skanne QR-koden på datamaskinen din';
|
@override String get noDevicesHint => 'Sørg for at Plezy er åpen på datamaskinen din og at begge enhetene er på samme WiFi-nettverk';
|
||||||
@override String get tipWifi => 'Sørg for at begge enhetene er på samme WiFi-nettverk';
|
@override String get availableDevices => 'Tilgjengelige enheter';
|
||||||
@override String get cameraPermissionRequired => 'Kameratillatelse kreves for å skanne QR-koder.\nVennligst gi kameratilgang i enhetsinnstillingene.';
|
@override String get manualConnection => 'Manuell tilkobling';
|
||||||
@override String cameraError({required Object error}) => 'Kunne ikke starte kamera: ${error}';
|
@override String get cryptoInitFailed => 'Kunne ikke initialisere sikker tilkobling. Sørg for at du er logget inn på en Plex-konto.';
|
||||||
@override String get scanInstruction => 'Pek kameraet mot QR-koden som vises på datamaskinen din';
|
@override String get validationHostRequired => 'Vennligst oppgi vertsadresse';
|
||||||
@override String get invalidQrCode => 'Ugyldig QR-kodeformat';
|
@override String get validationHostFormat => 'Format må være IP:port (f.eks. 192.168.1.100:48632)';
|
||||||
@override String get validationHostRequired => 'Vennligst skriv inn vertsadresse';
|
@override String get connectionTimedOut => 'Tilkoblingen ble tidsavbrutt. Sørg for at begge enhetene er på samme nettverk.';
|
||||||
@override String get validationHostFormat => 'Formatet må være IP:port (f.eks. 192.168.1.100:48632)';
|
@override String get sessionNotFound => 'Kunne ikke finne enheten. Sørg for at Plezy kjører på verten.';
|
||||||
@override String get validationSessionIdRequired => 'Vennligst skriv inn en økt-ID';
|
@override String get authFailed => 'Autentisering mislyktes. Sørg for at begge enhetene bruker samme Plex-konto.';
|
||||||
@override String get validationSessionIdLength => 'Økt-ID må være 8 tegn';
|
|
||||||
@override String get validationPinRequired => 'Vennligst skriv inn en PIN';
|
|
||||||
@override String get validationPinLength => 'PIN må være 6 sifre';
|
|
||||||
@override String get connectionTimedOut => 'Tilkoblingen ble tidsavbrutt. Sjekk økt-ID og PIN.';
|
|
||||||
@override String get sessionNotFound => 'Kunne ikke finne økten. Sjekk legitimasjonen din.';
|
|
||||||
@override String failedToConnect({required Object error}) => 'Kunne ikke koble til: ${error}';
|
@override String failedToConnect({required Object error}) => 'Kunne ikke koble til: ${error}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1600,6 +1590,8 @@ extension on TranslationsNb {
|
|||||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||||
'settings.seekAndTiming' => 'Seek & Timing',
|
'settings.seekAndTiming' => 'Seek & Timing',
|
||||||
'settings.behavior' => 'Behavior',
|
'settings.behavior' => 'Behavior',
|
||||||
|
'settings.companionRemoteServer' => 'Companion Remote-server',
|
||||||
|
'settings.companionRemoteServerDescription' => 'Tillat mobilenheter på nettverket ditt å styre denne appen',
|
||||||
'search.hint' => 'Søk i filmer, serier, musikk...',
|
'search.hint' => 'Søk i filmer, serier, musikk...',
|
||||||
'search.tryDifferentTerm' => 'Prøv et annet søkeord',
|
'search.tryDifferentTerm' => 'Prøv et annet søkeord',
|
||||||
'search.searchYourMedia' => 'Søk i mediene dine',
|
'search.searchYourMedia' => 'Søk i mediene dine',
|
||||||
@@ -1881,10 +1873,10 @@ extension on TranslationsNb {
|
|||||||
'libraries.groupings.seasons' => 'Sesonger',
|
'libraries.groupings.seasons' => 'Sesonger',
|
||||||
'libraries.groupings.episodes' => 'Episoder',
|
'libraries.groupings.episodes' => 'Episoder',
|
||||||
'libraries.groupings.folders' => 'Mapper',
|
'libraries.groupings.folders' => 'Mapper',
|
||||||
'about.title' => 'Om',
|
|
||||||
'about.openSourceLicenses' => 'Åpen kildekode-lisenser',
|
|
||||||
_ => null,
|
_ => null,
|
||||||
} ?? switch (path) {
|
} ?? switch (path) {
|
||||||
|
'about.title' => 'Om',
|
||||||
|
'about.openSourceLicenses' => 'Åpen kildekode-lisenser',
|
||||||
'about.versionLabel' => ({required Object version}) => 'Versjon ${version}',
|
'about.versionLabel' => ({required Object version}) => 'Versjon ${version}',
|
||||||
'about.appDescription' => 'En vakker Plex-klient for Flutter',
|
'about.appDescription' => 'En vakker Plex-klient for Flutter',
|
||||||
'about.viewLicensesDescription' => 'Vis lisenser for tredjepartsbiblioteker',
|
'about.viewLicensesDescription' => 'Vis lisenser for tredjepartsbiblioteker',
|
||||||
@@ -2076,45 +2068,33 @@ extension on TranslationsNb {
|
|||||||
'companionRemote.remoteControl' => 'Fjernkontroll',
|
'companionRemote.remoteControl' => 'Fjernkontroll',
|
||||||
'companionRemote.controlDesktop' => 'Kontroller en stasjonær enhet',
|
'companionRemote.controlDesktop' => 'Kontroller en stasjonær enhet',
|
||||||
'companionRemote.connectedTo' => ({required Object name}) => 'Tilkoblet ${name}',
|
'companionRemote.connectedTo' => ({required Object name}) => 'Tilkoblet ${name}',
|
||||||
'companionRemote.session.creatingSession' => 'Oppretter fjernøkt...',
|
'companionRemote.session.startingServer' => 'Starter fjernserver...',
|
||||||
'companionRemote.session.failedToCreate' => 'Kunne ikke opprette fjernøkt:',
|
'companionRemote.session.failedToCreate' => 'Kunne ikke starte fjernserver:',
|
||||||
'companionRemote.session.noSession' => 'Ingen økt tilgjengelig',
|
|
||||||
'companionRemote.session.scanQrCode' => 'Skann QR-kode',
|
|
||||||
'companionRemote.session.orEnterManually' => 'Eller skriv inn manuelt',
|
|
||||||
'companionRemote.session.hostAddress' => 'Vertsadresse',
|
'companionRemote.session.hostAddress' => 'Vertsadresse',
|
||||||
'companionRemote.session.sessionId' => 'Økt-ID',
|
|
||||||
'companionRemote.session.pin' => 'PIN',
|
|
||||||
'companionRemote.session.connected' => 'Tilkoblet',
|
'companionRemote.session.connected' => 'Tilkoblet',
|
||||||
'companionRemote.session.waitingForConnection' => 'Venter på tilkobling...',
|
'companionRemote.session.serverRunning' => 'Fjernserver aktiv',
|
||||||
'companionRemote.session.usePhoneToControl' => 'Bruk mobilenheten din til å kontrollere denne appen',
|
'companionRemote.session.serverStopped' => 'Fjernserver stoppet',
|
||||||
'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} kopiert til utklippstavle',
|
'companionRemote.session.serverRunningDescription' => 'Mobilenheter på nettverket ditt kan oppdage og koble til denne appen',
|
||||||
'companionRemote.session.copyToClipboard' => 'Kopier til utklippstavle',
|
'companionRemote.session.serverStoppedDescription' => 'Start serveren for å la mobilenheter koble til',
|
||||||
'companionRemote.session.newSession' => 'Ny økt',
|
'companionRemote.session.usePhoneToControl' => 'Bruk mobilenheten din til å styre denne appen',
|
||||||
|
'companionRemote.session.startServer' => 'Start server',
|
||||||
|
'companionRemote.session.stopServer' => 'Stopp server',
|
||||||
'companionRemote.session.minimize' => 'Minimer',
|
'companionRemote.session.minimize' => 'Minimer',
|
||||||
'companionRemote.pairing.scan' => 'Skann',
|
'companionRemote.pairing.pairWithDesktop' => 'Koble til datamaskin',
|
||||||
'companionRemote.pairing.manual' => 'Manuell',
|
'companionRemote.pairing.discoveryDescription' => 'Enheter på nettverket ditt som kjører Plezy med samme Plex-konto vil vises automatisk',
|
||||||
'companionRemote.pairing.pairWithDesktop' => 'Koble til stasjonær',
|
|
||||||
'companionRemote.pairing.enterSessionDetails' => 'Skriv inn øktdetaljene som vises på den stasjonære enheten din',
|
|
||||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||||
'companionRemote.pairing.sessionIdHint' => 'Skriv inn 8-tegns økt-ID',
|
|
||||||
'companionRemote.pairing.pinHint' => 'Skriv inn 6-sifret PIN',
|
|
||||||
'companionRemote.pairing.connecting' => 'Kobler til...',
|
'companionRemote.pairing.connecting' => 'Kobler til...',
|
||||||
'companionRemote.pairing.tips' => 'Tips',
|
'companionRemote.pairing.searchingForDevices' => 'Søker etter enheter...',
|
||||||
'companionRemote.pairing.tipDesktop' => 'Åpne Plezy på datamaskinen din og aktiver Følgesvenn-fjernkontroll fra innstillinger eller meny',
|
'companionRemote.pairing.noDevicesFound' => 'Ingen enheter funnet på nettverket ditt',
|
||||||
'companionRemote.pairing.tipScan' => 'Bruk Skann-fanen for å raskt koble til ved å skanne QR-koden på datamaskinen din',
|
'companionRemote.pairing.noDevicesHint' => 'Sørg for at Plezy er åpen på datamaskinen din og at begge enhetene er på samme WiFi-nettverk',
|
||||||
'companionRemote.pairing.tipWifi' => 'Sørg for at begge enhetene er på samme WiFi-nettverk',
|
'companionRemote.pairing.availableDevices' => 'Tilgjengelige enheter',
|
||||||
'companionRemote.pairing.cameraPermissionRequired' => 'Kameratillatelse kreves for å skanne QR-koder.\nVennligst gi kameratilgang i enhetsinnstillingene.',
|
'companionRemote.pairing.manualConnection' => 'Manuell tilkobling',
|
||||||
'companionRemote.pairing.cameraError' => ({required Object error}) => 'Kunne ikke starte kamera: ${error}',
|
'companionRemote.pairing.cryptoInitFailed' => 'Kunne ikke initialisere sikker tilkobling. Sørg for at du er logget inn på en Plex-konto.',
|
||||||
'companionRemote.pairing.scanInstruction' => 'Pek kameraet mot QR-koden som vises på datamaskinen din',
|
'companionRemote.pairing.validationHostRequired' => 'Vennligst oppgi vertsadresse',
|
||||||
'companionRemote.pairing.invalidQrCode' => 'Ugyldig QR-kodeformat',
|
'companionRemote.pairing.validationHostFormat' => 'Format må være IP:port (f.eks. 192.168.1.100:48632)',
|
||||||
'companionRemote.pairing.validationHostRequired' => 'Vennligst skriv inn vertsadresse',
|
'companionRemote.pairing.connectionTimedOut' => 'Tilkoblingen ble tidsavbrutt. Sørg for at begge enhetene er på samme nettverk.',
|
||||||
'companionRemote.pairing.validationHostFormat' => 'Formatet må være IP:port (f.eks. 192.168.1.100:48632)',
|
'companionRemote.pairing.sessionNotFound' => 'Kunne ikke finne enheten. Sørg for at Plezy kjører på verten.',
|
||||||
'companionRemote.pairing.validationSessionIdRequired' => 'Vennligst skriv inn en økt-ID',
|
'companionRemote.pairing.authFailed' => 'Autentisering mislyktes. Sørg for at begge enhetene bruker samme Plex-konto.',
|
||||||
'companionRemote.pairing.validationSessionIdLength' => 'Økt-ID må være 8 tegn',
|
|
||||||
'companionRemote.pairing.validationPinRequired' => 'Vennligst skriv inn en PIN',
|
|
||||||
'companionRemote.pairing.validationPinLength' => 'PIN må være 6 sifre',
|
|
||||||
'companionRemote.pairing.connectionTimedOut' => 'Tilkoblingen ble tidsavbrutt. Sjekk økt-ID og PIN.',
|
|
||||||
'companionRemote.pairing.sessionNotFound' => 'Kunne ikke finne økten. Sjekk legitimasjonen din.',
|
|
||||||
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Kunne ikke koble til: ${error}',
|
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Kunne ikke koble til: ${error}',
|
||||||
'companionRemote.remote.disconnectConfirm' => 'Vil du koble fra fjernøkten?',
|
'companionRemote.remote.disconnectConfirm' => 'Vil du koble fra fjernøkten?',
|
||||||
'companionRemote.remote.reconnecting' => 'Kobler til på nytt...',
|
'companionRemote.remote.reconnecting' => 'Kobler til på nytt...',
|
||||||
|
|||||||
+48
-68
@@ -358,6 +358,8 @@ class _TranslationsSettingsNl implements TranslationsSettingsEn {
|
|||||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||||
@override String get seekAndTiming => 'Seek & Timing';
|
@override String get seekAndTiming => 'Seek & Timing';
|
||||||
@override String get behavior => 'Behavior';
|
@override String get behavior => 'Behavior';
|
||||||
|
@override String get companionRemoteServer => 'Companion Remote-server';
|
||||||
|
@override String get companionRemoteServerDescription => 'Sta mobiele apparaten op je netwerk toe om deze app te bedienen';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: search
|
// Path: search
|
||||||
@@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionNl implements TranslationsCompanionRemo
|
|||||||
final TranslationsNl _root; // ignore: unused_field
|
final TranslationsNl _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get creatingSession => 'Externe sessie aanmaken...';
|
@override String get startingServer => 'Externe server starten...';
|
||||||
@override String get failedToCreate => 'Kan externe sessie niet aanmaken:';
|
@override String get failedToCreate => 'Kan externe server niet starten:';
|
||||||
@override String get noSession => 'Geen sessie beschikbaar';
|
|
||||||
@override String get scanQrCode => 'Scan QR-code';
|
|
||||||
@override String get orEnterManually => 'Of voer handmatig in';
|
|
||||||
@override String get hostAddress => 'Hostadres';
|
@override String get hostAddress => 'Hostadres';
|
||||||
@override String get sessionId => 'Sessie-ID';
|
|
||||||
@override String get pin => 'PIN';
|
|
||||||
@override String get connected => 'Verbonden';
|
@override String get connected => 'Verbonden';
|
||||||
@override String get waitingForConnection => 'Wachten op verbinding...';
|
@override String get serverRunning => 'Externe server actief';
|
||||||
|
@override String get serverStopped => 'Externe server gestopt';
|
||||||
|
@override String get serverRunningDescription => 'Mobiele apparaten op je netwerk kunnen deze app ontdekken en ermee verbinden';
|
||||||
|
@override String get serverStoppedDescription => 'Start de server om mobiele apparaten te laten verbinden';
|
||||||
@override String get usePhoneToControl => 'Gebruik je mobiele apparaat om deze app te bedienen';
|
@override String get usePhoneToControl => 'Gebruik je mobiele apparaat om deze app te bedienen';
|
||||||
@override String copiedToClipboard({required Object label}) => '${label} gekopieerd naar klembord';
|
@override String get startServer => 'Server starten';
|
||||||
@override String get copyToClipboard => 'Kopieer naar klembord';
|
@override String get stopServer => 'Server stoppen';
|
||||||
@override String get newSession => 'Nieuwe sessie';
|
|
||||||
@override String get minimize => 'Minimaliseren';
|
@override String get minimize => 'Minimaliseren';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1298,31 +1297,22 @@ class _TranslationsCompanionRemotePairingNl implements TranslationsCompanionRemo
|
|||||||
final TranslationsNl _root; // ignore: unused_field
|
final TranslationsNl _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get scan => 'Scannen';
|
@override String get pairWithDesktop => 'Verbinden met desktop';
|
||||||
@override String get manual => 'Handmatig';
|
@override String get discoveryDescription => 'Apparaten op je netwerk die Plezy gebruiken met hetzelfde Plex-account verschijnen automatisch';
|
||||||
@override String get pairWithDesktop => 'Koppelen met desktop';
|
|
||||||
@override String get enterSessionDetails => 'Voer de sessiegegevens in die op je desktop-apparaat worden getoond';
|
|
||||||
@override String get hostAddressHint => '192.168.1.100:48632';
|
@override String get hostAddressHint => '192.168.1.100:48632';
|
||||||
@override String get sessionIdHint => 'Voer 8-tekens sessie-ID in';
|
|
||||||
@override String get pinHint => 'Voer 6-cijferige PIN in';
|
|
||||||
@override String get connecting => 'Verbinden...';
|
@override String get connecting => 'Verbinden...';
|
||||||
@override String get tips => 'Tips';
|
@override String get searchingForDevices => 'Apparaten zoeken...';
|
||||||
@override String get tipDesktop => 'Open Plezy op je desktop en schakel Companion Remote in via instellingen of menu';
|
@override String get noDevicesFound => 'Geen apparaten gevonden op je netwerk';
|
||||||
@override String get tipScan => 'Gebruik het tabblad Scannen om snel te koppelen door de QR-code op je desktop te scannen';
|
@override String get noDevicesHint => 'Zorg ervoor dat Plezy geopend is op je desktop en dat beide apparaten op hetzelfde WiFi-netwerk zitten';
|
||||||
@override String get tipWifi => 'Zorg ervoor dat beide apparaten op hetzelfde WiFi-netwerk zitten';
|
@override String get availableDevices => 'Beschikbare apparaten';
|
||||||
@override String get cameraPermissionRequired => 'Cameratoestemming is vereist om QR-codes te scannen.\nGeef cameratoegang in je apparaatinstellingen.';
|
@override String get manualConnection => 'Handmatige verbinding';
|
||||||
@override String cameraError({required Object error}) => 'Kan camera niet starten: ${error}';
|
@override String get cryptoInitFailed => 'Kan beveiligde verbinding niet initialiseren. Zorg ervoor dat je bent ingelogd bij een Plex-account.';
|
||||||
@override String get scanInstruction => 'Richt je camera op de QR-code die op je desktop wordt getoond';
|
@override String get validationHostRequired => 'Voer het hostadres in';
|
||||||
@override String get invalidQrCode => 'Ongeldig QR-codeformaat';
|
|
||||||
@override String get validationHostRequired => 'Voer een hostadres in';
|
|
||||||
@override String get validationHostFormat => 'Formaat moet IP:poort zijn (bijv. 192.168.1.100:48632)';
|
@override String get validationHostFormat => 'Formaat moet IP:poort zijn (bijv. 192.168.1.100:48632)';
|
||||||
@override String get validationSessionIdRequired => 'Voer een sessie-ID in';
|
@override String get connectionTimedOut => 'Verbinding verlopen. Zorg ervoor dat beide apparaten op hetzelfde netwerk zitten.';
|
||||||
@override String get validationSessionIdLength => 'Sessie-ID moet 8 tekens zijn';
|
@override String get sessionNotFound => 'Apparaat niet gevonden. Zorg ervoor dat Plezy draait op de host.';
|
||||||
@override String get validationPinRequired => 'Voer een PIN in';
|
@override String get authFailed => 'Authenticatie mislukt. Zorg ervoor dat beide apparaten hetzelfde Plex-account gebruiken.';
|
||||||
@override String get validationPinLength => 'PIN moet 6 cijfers zijn';
|
@override String failedToConnect({required Object error}) => 'Kan niet verbinden: ${error}';
|
||||||
@override String get connectionTimedOut => 'Verbinding verlopen. Controleer de sessie-ID en PIN.';
|
|
||||||
@override String get sessionNotFound => 'Kan de sessie niet vinden. Controleer je gegevens.';
|
|
||||||
@override String failedToConnect({required Object error}) => 'Verbinden mislukt: ${error}';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: companionRemote.remote
|
// Path: companionRemote.remote
|
||||||
@@ -1600,6 +1590,8 @@ extension on TranslationsNl {
|
|||||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||||
'settings.seekAndTiming' => 'Seek & Timing',
|
'settings.seekAndTiming' => 'Seek & Timing',
|
||||||
'settings.behavior' => 'Behavior',
|
'settings.behavior' => 'Behavior',
|
||||||
|
'settings.companionRemoteServer' => 'Companion Remote-server',
|
||||||
|
'settings.companionRemoteServerDescription' => 'Sta mobiele apparaten op je netwerk toe om deze app te bedienen',
|
||||||
'search.hint' => 'Zoek films, series, muziek...',
|
'search.hint' => 'Zoek films, series, muziek...',
|
||||||
'search.tryDifferentTerm' => 'Probeer een andere zoekterm',
|
'search.tryDifferentTerm' => 'Probeer een andere zoekterm',
|
||||||
'search.searchYourMedia' => 'Zoek in je media',
|
'search.searchYourMedia' => 'Zoek in je media',
|
||||||
@@ -1881,10 +1873,10 @@ extension on TranslationsNl {
|
|||||||
'libraries.groupings.seasons' => 'Seizoenen',
|
'libraries.groupings.seasons' => 'Seizoenen',
|
||||||
'libraries.groupings.episodes' => 'Afleveringen',
|
'libraries.groupings.episodes' => 'Afleveringen',
|
||||||
'libraries.groupings.folders' => 'Mappen',
|
'libraries.groupings.folders' => 'Mappen',
|
||||||
'about.title' => 'Over',
|
|
||||||
'about.openSourceLicenses' => 'Open Source licenties',
|
|
||||||
_ => null,
|
_ => null,
|
||||||
} ?? switch (path) {
|
} ?? switch (path) {
|
||||||
|
'about.title' => 'Over',
|
||||||
|
'about.openSourceLicenses' => 'Open Source licenties',
|
||||||
'about.versionLabel' => ({required Object version}) => 'Versie ${version}',
|
'about.versionLabel' => ({required Object version}) => 'Versie ${version}',
|
||||||
'about.appDescription' => 'Een mooie Plex client voor Flutter',
|
'about.appDescription' => 'Een mooie Plex client voor Flutter',
|
||||||
'about.viewLicensesDescription' => 'Bekijk licenties van third-party bibliotheken',
|
'about.viewLicensesDescription' => 'Bekijk licenties van third-party bibliotheken',
|
||||||
@@ -2076,46 +2068,34 @@ extension on TranslationsNl {
|
|||||||
'companionRemote.remoteControl' => 'Afstandsbediening',
|
'companionRemote.remoteControl' => 'Afstandsbediening',
|
||||||
'companionRemote.controlDesktop' => 'Bedien een desktop-apparaat',
|
'companionRemote.controlDesktop' => 'Bedien een desktop-apparaat',
|
||||||
'companionRemote.connectedTo' => ({required Object name}) => 'Verbonden met ${name}',
|
'companionRemote.connectedTo' => ({required Object name}) => 'Verbonden met ${name}',
|
||||||
'companionRemote.session.creatingSession' => 'Externe sessie aanmaken...',
|
'companionRemote.session.startingServer' => 'Externe server starten...',
|
||||||
'companionRemote.session.failedToCreate' => 'Kan externe sessie niet aanmaken:',
|
'companionRemote.session.failedToCreate' => 'Kan externe server niet starten:',
|
||||||
'companionRemote.session.noSession' => 'Geen sessie beschikbaar',
|
|
||||||
'companionRemote.session.scanQrCode' => 'Scan QR-code',
|
|
||||||
'companionRemote.session.orEnterManually' => 'Of voer handmatig in',
|
|
||||||
'companionRemote.session.hostAddress' => 'Hostadres',
|
'companionRemote.session.hostAddress' => 'Hostadres',
|
||||||
'companionRemote.session.sessionId' => 'Sessie-ID',
|
|
||||||
'companionRemote.session.pin' => 'PIN',
|
|
||||||
'companionRemote.session.connected' => 'Verbonden',
|
'companionRemote.session.connected' => 'Verbonden',
|
||||||
'companionRemote.session.waitingForConnection' => 'Wachten op verbinding...',
|
'companionRemote.session.serverRunning' => 'Externe server actief',
|
||||||
|
'companionRemote.session.serverStopped' => 'Externe server gestopt',
|
||||||
|
'companionRemote.session.serverRunningDescription' => 'Mobiele apparaten op je netwerk kunnen deze app ontdekken en ermee verbinden',
|
||||||
|
'companionRemote.session.serverStoppedDescription' => 'Start de server om mobiele apparaten te laten verbinden',
|
||||||
'companionRemote.session.usePhoneToControl' => 'Gebruik je mobiele apparaat om deze app te bedienen',
|
'companionRemote.session.usePhoneToControl' => 'Gebruik je mobiele apparaat om deze app te bedienen',
|
||||||
'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} gekopieerd naar klembord',
|
'companionRemote.session.startServer' => 'Server starten',
|
||||||
'companionRemote.session.copyToClipboard' => 'Kopieer naar klembord',
|
'companionRemote.session.stopServer' => 'Server stoppen',
|
||||||
'companionRemote.session.newSession' => 'Nieuwe sessie',
|
|
||||||
'companionRemote.session.minimize' => 'Minimaliseren',
|
'companionRemote.session.minimize' => 'Minimaliseren',
|
||||||
'companionRemote.pairing.scan' => 'Scannen',
|
'companionRemote.pairing.pairWithDesktop' => 'Verbinden met desktop',
|
||||||
'companionRemote.pairing.manual' => 'Handmatig',
|
'companionRemote.pairing.discoveryDescription' => 'Apparaten op je netwerk die Plezy gebruiken met hetzelfde Plex-account verschijnen automatisch',
|
||||||
'companionRemote.pairing.pairWithDesktop' => 'Koppelen met desktop',
|
|
||||||
'companionRemote.pairing.enterSessionDetails' => 'Voer de sessiegegevens in die op je desktop-apparaat worden getoond',
|
|
||||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||||
'companionRemote.pairing.sessionIdHint' => 'Voer 8-tekens sessie-ID in',
|
|
||||||
'companionRemote.pairing.pinHint' => 'Voer 6-cijferige PIN in',
|
|
||||||
'companionRemote.pairing.connecting' => 'Verbinden...',
|
'companionRemote.pairing.connecting' => 'Verbinden...',
|
||||||
'companionRemote.pairing.tips' => 'Tips',
|
'companionRemote.pairing.searchingForDevices' => 'Apparaten zoeken...',
|
||||||
'companionRemote.pairing.tipDesktop' => 'Open Plezy op je desktop en schakel Companion Remote in via instellingen of menu',
|
'companionRemote.pairing.noDevicesFound' => 'Geen apparaten gevonden op je netwerk',
|
||||||
'companionRemote.pairing.tipScan' => 'Gebruik het tabblad Scannen om snel te koppelen door de QR-code op je desktop te scannen',
|
'companionRemote.pairing.noDevicesHint' => 'Zorg ervoor dat Plezy geopend is op je desktop en dat beide apparaten op hetzelfde WiFi-netwerk zitten',
|
||||||
'companionRemote.pairing.tipWifi' => 'Zorg ervoor dat beide apparaten op hetzelfde WiFi-netwerk zitten',
|
'companionRemote.pairing.availableDevices' => 'Beschikbare apparaten',
|
||||||
'companionRemote.pairing.cameraPermissionRequired' => 'Cameratoestemming is vereist om QR-codes te scannen.\nGeef cameratoegang in je apparaatinstellingen.',
|
'companionRemote.pairing.manualConnection' => 'Handmatige verbinding',
|
||||||
'companionRemote.pairing.cameraError' => ({required Object error}) => 'Kan camera niet starten: ${error}',
|
'companionRemote.pairing.cryptoInitFailed' => 'Kan beveiligde verbinding niet initialiseren. Zorg ervoor dat je bent ingelogd bij een Plex-account.',
|
||||||
'companionRemote.pairing.scanInstruction' => 'Richt je camera op de QR-code die op je desktop wordt getoond',
|
'companionRemote.pairing.validationHostRequired' => 'Voer het hostadres in',
|
||||||
'companionRemote.pairing.invalidQrCode' => 'Ongeldig QR-codeformaat',
|
|
||||||
'companionRemote.pairing.validationHostRequired' => 'Voer een hostadres in',
|
|
||||||
'companionRemote.pairing.validationHostFormat' => 'Formaat moet IP:poort zijn (bijv. 192.168.1.100:48632)',
|
'companionRemote.pairing.validationHostFormat' => 'Formaat moet IP:poort zijn (bijv. 192.168.1.100:48632)',
|
||||||
'companionRemote.pairing.validationSessionIdRequired' => 'Voer een sessie-ID in',
|
'companionRemote.pairing.connectionTimedOut' => 'Verbinding verlopen. Zorg ervoor dat beide apparaten op hetzelfde netwerk zitten.',
|
||||||
'companionRemote.pairing.validationSessionIdLength' => 'Sessie-ID moet 8 tekens zijn',
|
'companionRemote.pairing.sessionNotFound' => 'Apparaat niet gevonden. Zorg ervoor dat Plezy draait op de host.',
|
||||||
'companionRemote.pairing.validationPinRequired' => 'Voer een PIN in',
|
'companionRemote.pairing.authFailed' => 'Authenticatie mislukt. Zorg ervoor dat beide apparaten hetzelfde Plex-account gebruiken.',
|
||||||
'companionRemote.pairing.validationPinLength' => 'PIN moet 6 cijfers zijn',
|
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Kan niet verbinden: ${error}',
|
||||||
'companionRemote.pairing.connectionTimedOut' => 'Verbinding verlopen. Controleer de sessie-ID en PIN.',
|
|
||||||
'companionRemote.pairing.sessionNotFound' => 'Kan de sessie niet vinden. Controleer je gegevens.',
|
|
||||||
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Verbinden mislukt: ${error}',
|
|
||||||
'companionRemote.remote.disconnectConfirm' => 'Wil je de verbinding met de externe sessie verbreken?',
|
'companionRemote.remote.disconnectConfirm' => 'Wil je de verbinding met de externe sessie verbreken?',
|
||||||
'companionRemote.remote.reconnecting' => 'Opnieuw verbinden...',
|
'companionRemote.remote.reconnecting' => 'Opnieuw verbinden...',
|
||||||
'companionRemote.remote.attemptOf' => ({required Object current}) => 'Poging ${current} van 5',
|
'companionRemote.remote.attemptOf' => ({required Object current}) => 'Poging ${current} van 5',
|
||||||
|
|||||||
+46
-66
@@ -358,6 +358,8 @@ class _TranslationsSettingsPl implements TranslationsSettingsEn {
|
|||||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||||
@override String get seekAndTiming => 'Seek & Timing';
|
@override String get seekAndTiming => 'Seek & Timing';
|
||||||
@override String get behavior => 'Behavior';
|
@override String get behavior => 'Behavior';
|
||||||
|
@override String get companionRemoteServer => 'Serwer zdalnego sterowania';
|
||||||
|
@override String get companionRemoteServerDescription => 'Pozwól urządzeniom mobilnym w sieci sterować tą aplikacją';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: search
|
// Path: search
|
||||||
@@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionPl implements TranslationsCompanionRemo
|
|||||||
final TranslationsPl _root; // ignore: unused_field
|
final TranslationsPl _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get creatingSession => 'Tworzenie sesji zdalnej...';
|
@override String get startingServer => 'Uruchamianie serwera zdalnego...';
|
||||||
@override String get failedToCreate => 'Nie udało się utworzyć sesji zdalnej:';
|
@override String get failedToCreate => 'Nie udało się uruchomić serwera zdalnego:';
|
||||||
@override String get noSession => 'Brak dostępnej sesji';
|
|
||||||
@override String get scanQrCode => 'Zeskanuj kod QR';
|
|
||||||
@override String get orEnterManually => 'Lub wprowadź ręcznie';
|
|
||||||
@override String get hostAddress => 'Adres hosta';
|
@override String get hostAddress => 'Adres hosta';
|
||||||
@override String get sessionId => 'ID sesji';
|
|
||||||
@override String get pin => 'PIN';
|
|
||||||
@override String get connected => 'Połączono';
|
@override String get connected => 'Połączono';
|
||||||
@override String get waitingForConnection => 'Oczekiwanie na połączenie...';
|
@override String get serverRunning => 'Serwer zdalny aktywny';
|
||||||
@override String get usePhoneToControl => 'Użyj urządzenia mobilnego do sterowania tą aplikacją';
|
@override String get serverStopped => 'Serwer zdalny zatrzymany';
|
||||||
@override String copiedToClipboard({required Object label}) => '${label} skopiowane do schowka';
|
@override String get serverRunningDescription => 'Urządzenia mobilne w sieci mogą wykryć tę aplikację i się z nią połączyć';
|
||||||
@override String get copyToClipboard => 'Kopiuj do schowka';
|
@override String get serverStoppedDescription => 'Uruchom serwer, aby umożliwić połączenie urządzeń mobilnych';
|
||||||
@override String get newSession => 'Nowa sesja';
|
@override String get usePhoneToControl => 'Użyj urządzenia mobilnego, aby sterować tą aplikacją';
|
||||||
|
@override String get startServer => 'Uruchom serwer';
|
||||||
|
@override String get stopServer => 'Zatrzymaj serwer';
|
||||||
@override String get minimize => 'Minimalizuj';
|
@override String get minimize => 'Minimalizuj';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingPl implements TranslationsCompanionRemo
|
|||||||
final TranslationsPl _root; // ignore: unused_field
|
final TranslationsPl _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get scan => 'Skanuj';
|
@override String get pairWithDesktop => 'Połącz z komputerem';
|
||||||
@override String get manual => 'Ręcznie';
|
@override String get discoveryDescription => 'Urządzenia w sieci z Plezy na tym samym koncie Plex pojawią się automatycznie';
|
||||||
@override String get pairWithDesktop => 'Sparuj z komputerem';
|
|
||||||
@override String get enterSessionDetails => 'Wprowadź dane sesji wyświetlone na urządzeniu desktop';
|
|
||||||
@override String get hostAddressHint => '192.168.1.100:48632';
|
@override String get hostAddressHint => '192.168.1.100:48632';
|
||||||
@override String get sessionIdHint => 'Wprowadź 8-znakowe ID sesji';
|
|
||||||
@override String get pinHint => 'Wprowadź 6-cyfrowy PIN';
|
|
||||||
@override String get connecting => 'Łączenie...';
|
@override String get connecting => 'Łączenie...';
|
||||||
@override String get tips => 'Wskazówki';
|
@override String get searchingForDevices => 'Szukanie urządzeń...';
|
||||||
@override String get tipDesktop => 'Otwórz Plezy na komputerze i włącz Pilota zdalnego w ustawieniach lub menu';
|
@override String get noDevicesFound => 'Nie znaleziono urządzeń w sieci';
|
||||||
@override String get tipScan => 'Użyj zakładki Skanuj, aby szybko sparować skanując kod QR na komputerze';
|
@override String get noDevicesHint => 'Upewnij się, że Plezy jest otwarte na komputerze i oba urządzenia są w tej samej sieci WiFi';
|
||||||
@override String get tipWifi => 'Upewnij się, że oba urządzenia są w tej samej sieci WiFi';
|
@override String get availableDevices => 'Dostępne urządzenia';
|
||||||
@override String get cameraPermissionRequired => 'Uprawnienie do kamery jest wymagane do skanowania kodów QR.\nPrzyznaj dostęp do kamery w ustawieniach urządzenia.';
|
@override String get manualConnection => 'Połączenie ręczne';
|
||||||
@override String cameraError({required Object error}) => 'Nie udało się uruchomić kamery: ${error}';
|
@override String get cryptoInitFailed => 'Nie udało się zainicjować bezpiecznego połączenia. Upewnij się, że jesteś zalogowany na konto Plex.';
|
||||||
@override String get scanInstruction => 'Skieruj kamerę na kod QR wyświetlony na komputerze';
|
|
||||||
@override String get invalidQrCode => 'Nieprawidłowy format kodu QR';
|
|
||||||
@override String get validationHostRequired => 'Wprowadź adres hosta';
|
@override String get validationHostRequired => 'Wprowadź adres hosta';
|
||||||
@override String get validationHostFormat => 'Format musi być IP:port (np. 192.168.1.100:48632)';
|
@override String get validationHostFormat => 'Format musi być IP:port (np. 192.168.1.100:48632)';
|
||||||
@override String get validationSessionIdRequired => 'Wprowadź ID sesji';
|
@override String get connectionTimedOut => 'Przekroczono limit czasu połączenia. Upewnij się, że oba urządzenia są w tej samej sieci.';
|
||||||
@override String get validationSessionIdLength => 'ID sesji musi mieć 8 znaków';
|
@override String get sessionNotFound => 'Nie znaleziono urządzenia. Upewnij się, że Plezy działa na hoście.';
|
||||||
@override String get validationPinRequired => 'Wprowadź PIN';
|
@override String get authFailed => 'Uwierzytelnianie nie powiodło się. Upewnij się, że oba urządzenia używają tego samego konta Plex.';
|
||||||
@override String get validationPinLength => 'PIN musi mieć 6 cyfr';
|
|
||||||
@override String get connectionTimedOut => 'Upłynął czas połączenia. Sprawdź ID sesji i PIN.';
|
|
||||||
@override String get sessionNotFound => 'Nie znaleziono sesji. Sprawdź swoje dane.';
|
|
||||||
@override String failedToConnect({required Object error}) => 'Nie udało się połączyć: ${error}';
|
@override String failedToConnect({required Object error}) => 'Nie udało się połączyć: ${error}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1600,6 +1590,8 @@ extension on TranslationsPl {
|
|||||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||||
'settings.seekAndTiming' => 'Seek & Timing',
|
'settings.seekAndTiming' => 'Seek & Timing',
|
||||||
'settings.behavior' => 'Behavior',
|
'settings.behavior' => 'Behavior',
|
||||||
|
'settings.companionRemoteServer' => 'Serwer zdalnego sterowania',
|
||||||
|
'settings.companionRemoteServerDescription' => 'Pozwól urządzeniom mobilnym w sieci sterować tą aplikacją',
|
||||||
'search.hint' => 'Szukaj filmów, seriali, muzyki...',
|
'search.hint' => 'Szukaj filmów, seriali, muzyki...',
|
||||||
'search.tryDifferentTerm' => 'Spróbuj innego wyszukiwania',
|
'search.tryDifferentTerm' => 'Spróbuj innego wyszukiwania',
|
||||||
'search.searchYourMedia' => 'Przeszukaj swoje media',
|
'search.searchYourMedia' => 'Przeszukaj swoje media',
|
||||||
@@ -1881,10 +1873,10 @@ extension on TranslationsPl {
|
|||||||
'libraries.groupings.seasons' => 'Sezony',
|
'libraries.groupings.seasons' => 'Sezony',
|
||||||
'libraries.groupings.episodes' => 'Odcinki',
|
'libraries.groupings.episodes' => 'Odcinki',
|
||||||
'libraries.groupings.folders' => 'Foldery',
|
'libraries.groupings.folders' => 'Foldery',
|
||||||
'about.title' => 'O aplikacji',
|
|
||||||
'about.openSourceLicenses' => 'Licencje open source',
|
|
||||||
_ => null,
|
_ => null,
|
||||||
} ?? switch (path) {
|
} ?? switch (path) {
|
||||||
|
'about.title' => 'O aplikacji',
|
||||||
|
'about.openSourceLicenses' => 'Licencje open source',
|
||||||
'about.versionLabel' => ({required Object version}) => 'Wersja ${version}',
|
'about.versionLabel' => ({required Object version}) => 'Wersja ${version}',
|
||||||
'about.appDescription' => 'Piękny klient Plex na Flutter',
|
'about.appDescription' => 'Piękny klient Plex na Flutter',
|
||||||
'about.viewLicensesDescription' => 'Zobacz licencje bibliotek zewnętrznych',
|
'about.viewLicensesDescription' => 'Zobacz licencje bibliotek zewnętrznych',
|
||||||
@@ -2076,45 +2068,33 @@ extension on TranslationsPl {
|
|||||||
'companionRemote.remoteControl' => 'Pilot zdalny',
|
'companionRemote.remoteControl' => 'Pilot zdalny',
|
||||||
'companionRemote.controlDesktop' => 'Steruj urządzeniem desktop',
|
'companionRemote.controlDesktop' => 'Steruj urządzeniem desktop',
|
||||||
'companionRemote.connectedTo' => ({required Object name}) => 'Połączono z ${name}',
|
'companionRemote.connectedTo' => ({required Object name}) => 'Połączono z ${name}',
|
||||||
'companionRemote.session.creatingSession' => 'Tworzenie sesji zdalnej...',
|
'companionRemote.session.startingServer' => 'Uruchamianie serwera zdalnego...',
|
||||||
'companionRemote.session.failedToCreate' => 'Nie udało się utworzyć sesji zdalnej:',
|
'companionRemote.session.failedToCreate' => 'Nie udało się uruchomić serwera zdalnego:',
|
||||||
'companionRemote.session.noSession' => 'Brak dostępnej sesji',
|
|
||||||
'companionRemote.session.scanQrCode' => 'Zeskanuj kod QR',
|
|
||||||
'companionRemote.session.orEnterManually' => 'Lub wprowadź ręcznie',
|
|
||||||
'companionRemote.session.hostAddress' => 'Adres hosta',
|
'companionRemote.session.hostAddress' => 'Adres hosta',
|
||||||
'companionRemote.session.sessionId' => 'ID sesji',
|
|
||||||
'companionRemote.session.pin' => 'PIN',
|
|
||||||
'companionRemote.session.connected' => 'Połączono',
|
'companionRemote.session.connected' => 'Połączono',
|
||||||
'companionRemote.session.waitingForConnection' => 'Oczekiwanie na połączenie...',
|
'companionRemote.session.serverRunning' => 'Serwer zdalny aktywny',
|
||||||
'companionRemote.session.usePhoneToControl' => 'Użyj urządzenia mobilnego do sterowania tą aplikacją',
|
'companionRemote.session.serverStopped' => 'Serwer zdalny zatrzymany',
|
||||||
'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} skopiowane do schowka',
|
'companionRemote.session.serverRunningDescription' => 'Urządzenia mobilne w sieci mogą wykryć tę aplikację i się z nią połączyć',
|
||||||
'companionRemote.session.copyToClipboard' => 'Kopiuj do schowka',
|
'companionRemote.session.serverStoppedDescription' => 'Uruchom serwer, aby umożliwić połączenie urządzeń mobilnych',
|
||||||
'companionRemote.session.newSession' => 'Nowa sesja',
|
'companionRemote.session.usePhoneToControl' => 'Użyj urządzenia mobilnego, aby sterować tą aplikacją',
|
||||||
|
'companionRemote.session.startServer' => 'Uruchom serwer',
|
||||||
|
'companionRemote.session.stopServer' => 'Zatrzymaj serwer',
|
||||||
'companionRemote.session.minimize' => 'Minimalizuj',
|
'companionRemote.session.minimize' => 'Minimalizuj',
|
||||||
'companionRemote.pairing.scan' => 'Skanuj',
|
'companionRemote.pairing.pairWithDesktop' => 'Połącz z komputerem',
|
||||||
'companionRemote.pairing.manual' => 'Ręcznie',
|
'companionRemote.pairing.discoveryDescription' => 'Urządzenia w sieci z Plezy na tym samym koncie Plex pojawią się automatycznie',
|
||||||
'companionRemote.pairing.pairWithDesktop' => 'Sparuj z komputerem',
|
|
||||||
'companionRemote.pairing.enterSessionDetails' => 'Wprowadź dane sesji wyświetlone na urządzeniu desktop',
|
|
||||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||||
'companionRemote.pairing.sessionIdHint' => 'Wprowadź 8-znakowe ID sesji',
|
|
||||||
'companionRemote.pairing.pinHint' => 'Wprowadź 6-cyfrowy PIN',
|
|
||||||
'companionRemote.pairing.connecting' => 'Łączenie...',
|
'companionRemote.pairing.connecting' => 'Łączenie...',
|
||||||
'companionRemote.pairing.tips' => 'Wskazówki',
|
'companionRemote.pairing.searchingForDevices' => 'Szukanie urządzeń...',
|
||||||
'companionRemote.pairing.tipDesktop' => 'Otwórz Plezy na komputerze i włącz Pilota zdalnego w ustawieniach lub menu',
|
'companionRemote.pairing.noDevicesFound' => 'Nie znaleziono urządzeń w sieci',
|
||||||
'companionRemote.pairing.tipScan' => 'Użyj zakładki Skanuj, aby szybko sparować skanując kod QR na komputerze',
|
'companionRemote.pairing.noDevicesHint' => 'Upewnij się, że Plezy jest otwarte na komputerze i oba urządzenia są w tej samej sieci WiFi',
|
||||||
'companionRemote.pairing.tipWifi' => 'Upewnij się, że oba urządzenia są w tej samej sieci WiFi',
|
'companionRemote.pairing.availableDevices' => 'Dostępne urządzenia',
|
||||||
'companionRemote.pairing.cameraPermissionRequired' => 'Uprawnienie do kamery jest wymagane do skanowania kodów QR.\nPrzyznaj dostęp do kamery w ustawieniach urządzenia.',
|
'companionRemote.pairing.manualConnection' => 'Połączenie ręczne',
|
||||||
'companionRemote.pairing.cameraError' => ({required Object error}) => 'Nie udało się uruchomić kamery: ${error}',
|
'companionRemote.pairing.cryptoInitFailed' => 'Nie udało się zainicjować bezpiecznego połączenia. Upewnij się, że jesteś zalogowany na konto Plex.',
|
||||||
'companionRemote.pairing.scanInstruction' => 'Skieruj kamerę na kod QR wyświetlony na komputerze',
|
|
||||||
'companionRemote.pairing.invalidQrCode' => 'Nieprawidłowy format kodu QR',
|
|
||||||
'companionRemote.pairing.validationHostRequired' => 'Wprowadź adres hosta',
|
'companionRemote.pairing.validationHostRequired' => 'Wprowadź adres hosta',
|
||||||
'companionRemote.pairing.validationHostFormat' => 'Format musi być IP:port (np. 192.168.1.100:48632)',
|
'companionRemote.pairing.validationHostFormat' => 'Format musi być IP:port (np. 192.168.1.100:48632)',
|
||||||
'companionRemote.pairing.validationSessionIdRequired' => 'Wprowadź ID sesji',
|
'companionRemote.pairing.connectionTimedOut' => 'Przekroczono limit czasu połączenia. Upewnij się, że oba urządzenia są w tej samej sieci.',
|
||||||
'companionRemote.pairing.validationSessionIdLength' => 'ID sesji musi mieć 8 znaków',
|
'companionRemote.pairing.sessionNotFound' => 'Nie znaleziono urządzenia. Upewnij się, że Plezy działa na hoście.',
|
||||||
'companionRemote.pairing.validationPinRequired' => 'Wprowadź PIN',
|
'companionRemote.pairing.authFailed' => 'Uwierzytelnianie nie powiodło się. Upewnij się, że oba urządzenia używają tego samego konta Plex.',
|
||||||
'companionRemote.pairing.validationPinLength' => 'PIN musi mieć 6 cyfr',
|
|
||||||
'companionRemote.pairing.connectionTimedOut' => 'Upłynął czas połączenia. Sprawdź ID sesji i PIN.',
|
|
||||||
'companionRemote.pairing.sessionNotFound' => 'Nie znaleziono sesji. Sprawdź swoje dane.',
|
|
||||||
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Nie udało się połączyć: ${error}',
|
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Nie udało się połączyć: ${error}',
|
||||||
'companionRemote.remote.disconnectConfirm' => 'Czy chcesz się rozłączyć od sesji zdalnej?',
|
'companionRemote.remote.disconnectConfirm' => 'Czy chcesz się rozłączyć od sesji zdalnej?',
|
||||||
'companionRemote.remote.reconnecting' => 'Ponowne łączenie...',
|
'companionRemote.remote.reconnecting' => 'Ponowne łączenie...',
|
||||||
|
|||||||
+54
-74
@@ -358,6 +358,8 @@ class _TranslationsSettingsPt implements TranslationsSettingsEn {
|
|||||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||||
@override String get seekAndTiming => 'Seek & Timing';
|
@override String get seekAndTiming => 'Seek & Timing';
|
||||||
@override String get behavior => 'Behavior';
|
@override String get behavior => 'Behavior';
|
||||||
|
@override String get companionRemoteServer => 'Servidor de controlo remoto';
|
||||||
|
@override String get companionRemoteServerDescription => 'Permitir que dispositivos móveis na sua rede controlem esta aplicação';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: search
|
// Path: search
|
||||||
@@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionPt implements TranslationsCompanionRemo
|
|||||||
final TranslationsPt _root; // ignore: unused_field
|
final TranslationsPt _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get creatingSession => 'Criando sessão remota...';
|
@override String get startingServer => 'A iniciar servidor remoto...';
|
||||||
@override String get failedToCreate => 'Falha ao criar sessão remota:';
|
@override String get failedToCreate => 'Falha ao iniciar o servidor remoto:';
|
||||||
@override String get noSession => 'Nenhuma sessão disponível';
|
@override String get hostAddress => 'Endereço do host';
|
||||||
@override String get scanQrCode => 'Escanear QR Code';
|
|
||||||
@override String get orEnterManually => 'Ou insira manualmente';
|
|
||||||
@override String get hostAddress => 'Endereço do Host';
|
|
||||||
@override String get sessionId => 'ID da Sessão';
|
|
||||||
@override String get pin => 'PIN';
|
|
||||||
@override String get connected => 'Conectado';
|
@override String get connected => 'Conectado';
|
||||||
@override String get waitingForConnection => 'Aguardando conexão...';
|
@override String get serverRunning => 'Servidor remoto ativo';
|
||||||
@override String get usePhoneToControl => 'Use seu dispositivo móvel para controlar este app';
|
@override String get serverStopped => 'Servidor remoto parado';
|
||||||
@override String copiedToClipboard({required Object label}) => '${label} copiado para a área de transferência';
|
@override String get serverRunningDescription => 'Dispositivos móveis na sua rede podem descobrir e conectar-se a esta aplicação';
|
||||||
@override String get copyToClipboard => 'Copiar para área de transferência';
|
@override String get serverStoppedDescription => 'Inicie o servidor para permitir que dispositivos móveis se conectem';
|
||||||
@override String get newSession => 'Nova Sessão';
|
@override String get usePhoneToControl => 'Use o seu dispositivo móvel para controlar esta aplicação';
|
||||||
|
@override String get startServer => 'Iniciar servidor';
|
||||||
|
@override String get stopServer => 'Parar servidor';
|
||||||
@override String get minimize => 'Minimizar';
|
@override String get minimize => 'Minimizar';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingPt implements TranslationsCompanionRemo
|
|||||||
final TranslationsPt _root; // ignore: unused_field
|
final TranslationsPt _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get scan => 'Escanear';
|
@override String get pairWithDesktop => 'Conectar ao desktop';
|
||||||
@override String get manual => 'Manual';
|
@override String get discoveryDescription => 'Dispositivos na sua rede a executar Plezy com a mesma conta Plex aparecerão automaticamente';
|
||||||
@override String get pairWithDesktop => 'Parear com Desktop';
|
|
||||||
@override String get enterSessionDetails => 'Insira os detalhes da sessão exibidos no seu dispositivo desktop';
|
|
||||||
@override String get hostAddressHint => '192.168.1.100:48632';
|
@override String get hostAddressHint => '192.168.1.100:48632';
|
||||||
@override String get sessionIdHint => 'Insira o ID da sessão de 8 caracteres';
|
@override String get connecting => 'A conectar...';
|
||||||
@override String get pinHint => 'Insira o PIN de 6 dígitos';
|
@override String get searchingForDevices => 'A procurar dispositivos...';
|
||||||
@override String get connecting => 'Conectando...';
|
@override String get noDevicesFound => 'Nenhum dispositivo encontrado na sua rede';
|
||||||
@override String get tips => 'Dicas';
|
@override String get noDevicesHint => 'Certifique-se de que o Plezy está aberto no seu desktop e que ambos os dispositivos estão na mesma rede WiFi';
|
||||||
@override String get tipDesktop => 'Abra o Plezy no desktop e ative o Controle Remoto nas configurações ou menu';
|
@override String get availableDevices => 'Dispositivos disponíveis';
|
||||||
@override String get tipScan => 'Use a aba Escanear para parear rapidamente escaneando o QR code no seu desktop';
|
@override String get manualConnection => 'Conexão manual';
|
||||||
@override String get tipWifi => 'Certifique-se de que ambos os dispositivos estão na mesma rede WiFi';
|
@override String get cryptoInitFailed => 'Não foi possível inicializar a conexão segura. Certifique-se de que está conectado a uma conta Plex.';
|
||||||
@override String get cameraPermissionRequired => 'A permissão da câmera é necessária para escanear QR codes.\nConceda acesso à câmera nas configurações do dispositivo.';
|
@override String get validationHostRequired => 'Introduza o endereço do host';
|
||||||
@override String cameraError({required Object error}) => 'Não foi possível iniciar a câmera: ${error}';
|
@override String get validationHostFormat => 'O formato deve ser IP:porta (ex. 192.168.1.100:48632)';
|
||||||
@override String get scanInstruction => 'Aponte sua câmera para o QR code exibido no seu desktop';
|
@override String get connectionTimedOut => 'Tempo de conexão esgotado. Certifique-se de que ambos os dispositivos estão na mesma rede.';
|
||||||
@override String get invalidQrCode => 'Formato de QR code inválido';
|
@override String get sessionNotFound => 'Dispositivo não encontrado. Certifique-se de que o Plezy está em execução no host.';
|
||||||
@override String get validationHostRequired => 'Insira o endereço do host';
|
@override String get authFailed => 'Autenticação falhou. Certifique-se de que ambos os dispositivos usam a mesma conta Plex.';
|
||||||
@override String get validationHostFormat => 'O formato deve ser IP:porta (ex: 192.168.1.100:48632)';
|
|
||||||
@override String get validationSessionIdRequired => 'Insira um ID de sessão';
|
|
||||||
@override String get validationSessionIdLength => 'O ID da sessão deve ter 8 caracteres';
|
|
||||||
@override String get validationPinRequired => 'Insira um PIN';
|
|
||||||
@override String get validationPinLength => 'O PIN deve ter 6 dígitos';
|
|
||||||
@override String get connectionTimedOut => 'Tempo de conexão esgotado. Verifique o ID da sessão e o PIN.';
|
|
||||||
@override String get sessionNotFound => 'Sessão não encontrada. Verifique suas credenciais.';
|
|
||||||
@override String failedToConnect({required Object error}) => 'Falha ao conectar: ${error}';
|
@override String failedToConnect({required Object error}) => 'Falha ao conectar: ${error}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1600,6 +1590,8 @@ extension on TranslationsPt {
|
|||||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||||
'settings.seekAndTiming' => 'Seek & Timing',
|
'settings.seekAndTiming' => 'Seek & Timing',
|
||||||
'settings.behavior' => 'Behavior',
|
'settings.behavior' => 'Behavior',
|
||||||
|
'settings.companionRemoteServer' => 'Servidor de controlo remoto',
|
||||||
|
'settings.companionRemoteServerDescription' => 'Permitir que dispositivos móveis na sua rede controlem esta aplicação',
|
||||||
'search.hint' => 'Buscar filmes, séries, músicas...',
|
'search.hint' => 'Buscar filmes, séries, músicas...',
|
||||||
'search.tryDifferentTerm' => 'Tente um termo de busca diferente',
|
'search.tryDifferentTerm' => 'Tente um termo de busca diferente',
|
||||||
'search.searchYourMedia' => 'Buscar suas mídias',
|
'search.searchYourMedia' => 'Buscar suas mídias',
|
||||||
@@ -1881,10 +1873,10 @@ extension on TranslationsPt {
|
|||||||
'libraries.groupings.seasons' => 'Temporadas',
|
'libraries.groupings.seasons' => 'Temporadas',
|
||||||
'libraries.groupings.episodes' => 'Episódios',
|
'libraries.groupings.episodes' => 'Episódios',
|
||||||
'libraries.groupings.folders' => 'Pastas',
|
'libraries.groupings.folders' => 'Pastas',
|
||||||
'about.title' => 'Sobre',
|
|
||||||
'about.openSourceLicenses' => 'Licenças Open Source',
|
|
||||||
_ => null,
|
_ => null,
|
||||||
} ?? switch (path) {
|
} ?? switch (path) {
|
||||||
|
'about.title' => 'Sobre',
|
||||||
|
'about.openSourceLicenses' => 'Licenças Open Source',
|
||||||
'about.versionLabel' => ({required Object version}) => 'Versão ${version}',
|
'about.versionLabel' => ({required Object version}) => 'Versão ${version}',
|
||||||
'about.appDescription' => 'Um belo cliente Plex para Flutter',
|
'about.appDescription' => 'Um belo cliente Plex para Flutter',
|
||||||
'about.viewLicensesDescription' => 'Ver licenças de bibliotecas de terceiros',
|
'about.viewLicensesDescription' => 'Ver licenças de bibliotecas de terceiros',
|
||||||
@@ -2076,45 +2068,33 @@ extension on TranslationsPt {
|
|||||||
'companionRemote.remoteControl' => 'Controle Remoto',
|
'companionRemote.remoteControl' => 'Controle Remoto',
|
||||||
'companionRemote.controlDesktop' => 'Controlar um dispositivo desktop',
|
'companionRemote.controlDesktop' => 'Controlar um dispositivo desktop',
|
||||||
'companionRemote.connectedTo' => ({required Object name}) => 'Conectado a ${name}',
|
'companionRemote.connectedTo' => ({required Object name}) => 'Conectado a ${name}',
|
||||||
'companionRemote.session.creatingSession' => 'Criando sessão remota...',
|
'companionRemote.session.startingServer' => 'A iniciar servidor remoto...',
|
||||||
'companionRemote.session.failedToCreate' => 'Falha ao criar sessão remota:',
|
'companionRemote.session.failedToCreate' => 'Falha ao iniciar o servidor remoto:',
|
||||||
'companionRemote.session.noSession' => 'Nenhuma sessão disponível',
|
'companionRemote.session.hostAddress' => 'Endereço do host',
|
||||||
'companionRemote.session.scanQrCode' => 'Escanear QR Code',
|
|
||||||
'companionRemote.session.orEnterManually' => 'Ou insira manualmente',
|
|
||||||
'companionRemote.session.hostAddress' => 'Endereço do Host',
|
|
||||||
'companionRemote.session.sessionId' => 'ID da Sessão',
|
|
||||||
'companionRemote.session.pin' => 'PIN',
|
|
||||||
'companionRemote.session.connected' => 'Conectado',
|
'companionRemote.session.connected' => 'Conectado',
|
||||||
'companionRemote.session.waitingForConnection' => 'Aguardando conexão...',
|
'companionRemote.session.serverRunning' => 'Servidor remoto ativo',
|
||||||
'companionRemote.session.usePhoneToControl' => 'Use seu dispositivo móvel para controlar este app',
|
'companionRemote.session.serverStopped' => 'Servidor remoto parado',
|
||||||
'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} copiado para a área de transferência',
|
'companionRemote.session.serverRunningDescription' => 'Dispositivos móveis na sua rede podem descobrir e conectar-se a esta aplicação',
|
||||||
'companionRemote.session.copyToClipboard' => 'Copiar para área de transferência',
|
'companionRemote.session.serverStoppedDescription' => 'Inicie o servidor para permitir que dispositivos móveis se conectem',
|
||||||
'companionRemote.session.newSession' => 'Nova Sessão',
|
'companionRemote.session.usePhoneToControl' => 'Use o seu dispositivo móvel para controlar esta aplicação',
|
||||||
|
'companionRemote.session.startServer' => 'Iniciar servidor',
|
||||||
|
'companionRemote.session.stopServer' => 'Parar servidor',
|
||||||
'companionRemote.session.minimize' => 'Minimizar',
|
'companionRemote.session.minimize' => 'Minimizar',
|
||||||
'companionRemote.pairing.scan' => 'Escanear',
|
'companionRemote.pairing.pairWithDesktop' => 'Conectar ao desktop',
|
||||||
'companionRemote.pairing.manual' => 'Manual',
|
'companionRemote.pairing.discoveryDescription' => 'Dispositivos na sua rede a executar Plezy com a mesma conta Plex aparecerão automaticamente',
|
||||||
'companionRemote.pairing.pairWithDesktop' => 'Parear com Desktop',
|
|
||||||
'companionRemote.pairing.enterSessionDetails' => 'Insira os detalhes da sessão exibidos no seu dispositivo desktop',
|
|
||||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||||
'companionRemote.pairing.sessionIdHint' => 'Insira o ID da sessão de 8 caracteres',
|
'companionRemote.pairing.connecting' => 'A conectar...',
|
||||||
'companionRemote.pairing.pinHint' => 'Insira o PIN de 6 dígitos',
|
'companionRemote.pairing.searchingForDevices' => 'A procurar dispositivos...',
|
||||||
'companionRemote.pairing.connecting' => 'Conectando...',
|
'companionRemote.pairing.noDevicesFound' => 'Nenhum dispositivo encontrado na sua rede',
|
||||||
'companionRemote.pairing.tips' => 'Dicas',
|
'companionRemote.pairing.noDevicesHint' => 'Certifique-se de que o Plezy está aberto no seu desktop e que ambos os dispositivos estão na mesma rede WiFi',
|
||||||
'companionRemote.pairing.tipDesktop' => 'Abra o Plezy no desktop e ative o Controle Remoto nas configurações ou menu',
|
'companionRemote.pairing.availableDevices' => 'Dispositivos disponíveis',
|
||||||
'companionRemote.pairing.tipScan' => 'Use a aba Escanear para parear rapidamente escaneando o QR code no seu desktop',
|
'companionRemote.pairing.manualConnection' => 'Conexão manual',
|
||||||
'companionRemote.pairing.tipWifi' => 'Certifique-se de que ambos os dispositivos estão na mesma rede WiFi',
|
'companionRemote.pairing.cryptoInitFailed' => 'Não foi possível inicializar a conexão segura. Certifique-se de que está conectado a uma conta Plex.',
|
||||||
'companionRemote.pairing.cameraPermissionRequired' => 'A permissão da câmera é necessária para escanear QR codes.\nConceda acesso à câmera nas configurações do dispositivo.',
|
'companionRemote.pairing.validationHostRequired' => 'Introduza o endereço do host',
|
||||||
'companionRemote.pairing.cameraError' => ({required Object error}) => 'Não foi possível iniciar a câmera: ${error}',
|
'companionRemote.pairing.validationHostFormat' => 'O formato deve ser IP:porta (ex. 192.168.1.100:48632)',
|
||||||
'companionRemote.pairing.scanInstruction' => 'Aponte sua câmera para o QR code exibido no seu desktop',
|
'companionRemote.pairing.connectionTimedOut' => 'Tempo de conexão esgotado. Certifique-se de que ambos os dispositivos estão na mesma rede.',
|
||||||
'companionRemote.pairing.invalidQrCode' => 'Formato de QR code inválido',
|
'companionRemote.pairing.sessionNotFound' => 'Dispositivo não encontrado. Certifique-se de que o Plezy está em execução no host.',
|
||||||
'companionRemote.pairing.validationHostRequired' => 'Insira o endereço do host',
|
'companionRemote.pairing.authFailed' => 'Autenticação falhou. Certifique-se de que ambos os dispositivos usam a mesma conta Plex.',
|
||||||
'companionRemote.pairing.validationHostFormat' => 'O formato deve ser IP:porta (ex: 192.168.1.100:48632)',
|
|
||||||
'companionRemote.pairing.validationSessionIdRequired' => 'Insira um ID de sessão',
|
|
||||||
'companionRemote.pairing.validationSessionIdLength' => 'O ID da sessão deve ter 8 caracteres',
|
|
||||||
'companionRemote.pairing.validationPinRequired' => 'Insira um PIN',
|
|
||||||
'companionRemote.pairing.validationPinLength' => 'O PIN deve ter 6 dígitos',
|
|
||||||
'companionRemote.pairing.connectionTimedOut' => 'Tempo de conexão esgotado. Verifique o ID da sessão e o PIN.',
|
|
||||||
'companionRemote.pairing.sessionNotFound' => 'Sessão não encontrada. Verifique suas credenciais.',
|
|
||||||
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Falha ao conectar: ${error}',
|
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Falha ao conectar: ${error}',
|
||||||
'companionRemote.remote.disconnectConfirm' => 'Deseja desconectar da sessão remota?',
|
'companionRemote.remote.disconnectConfirm' => 'Deseja desconectar da sessão remota?',
|
||||||
'companionRemote.remote.reconnecting' => 'Reconectando...',
|
'companionRemote.remote.reconnecting' => 'Reconectando...',
|
||||||
|
|||||||
+44
-64
@@ -358,6 +358,8 @@ class _TranslationsSettingsRu implements TranslationsSettingsEn {
|
|||||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||||
@override String get seekAndTiming => 'Seek & Timing';
|
@override String get seekAndTiming => 'Seek & Timing';
|
||||||
@override String get behavior => 'Behavior';
|
@override String get behavior => 'Behavior';
|
||||||
|
@override String get companionRemoteServer => 'Сервер удалённого управления';
|
||||||
|
@override String get companionRemoteServerDescription => 'Разрешить мобильным устройствам в сети управлять этим приложением';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: search
|
// Path: search
|
||||||
@@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionRu implements TranslationsCompanionRemo
|
|||||||
final TranslationsRu _root; // ignore: unused_field
|
final TranslationsRu _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get creatingSession => 'Создание удалённой сессии...';
|
@override String get startingServer => 'Запуск удалённого сервера...';
|
||||||
@override String get failedToCreate => 'Не удалось создать удалённую сессию:';
|
@override String get failedToCreate => 'Не удалось запустить удалённый сервер:';
|
||||||
@override String get noSession => 'Нет доступных сессий';
|
|
||||||
@override String get scanQrCode => 'Сканировать QR-код';
|
|
||||||
@override String get orEnterManually => 'Или введите вручную';
|
|
||||||
@override String get hostAddress => 'Адрес хоста';
|
@override String get hostAddress => 'Адрес хоста';
|
||||||
@override String get sessionId => 'ID сессии';
|
|
||||||
@override String get pin => 'PIN';
|
|
||||||
@override String get connected => 'Подключено';
|
@override String get connected => 'Подключено';
|
||||||
@override String get waitingForConnection => 'Ожидание подключения...';
|
@override String get serverRunning => 'Удалённый сервер активен';
|
||||||
|
@override String get serverStopped => 'Удалённый сервер остановлен';
|
||||||
|
@override String get serverRunningDescription => 'Мобильные устройства в вашей сети могут обнаруживать это приложение и подключаться к нему';
|
||||||
|
@override String get serverStoppedDescription => 'Запустите сервер, чтобы разрешить подключение мобильных устройств';
|
||||||
@override String get usePhoneToControl => 'Используйте мобильное устройство для управления этим приложением';
|
@override String get usePhoneToControl => 'Используйте мобильное устройство для управления этим приложением';
|
||||||
@override String copiedToClipboard({required Object label}) => '${label} скопировано в буфер обмена';
|
@override String get startServer => 'Запустить сервер';
|
||||||
@override String get copyToClipboard => 'Скопировать в буфер обмена';
|
@override String get stopServer => 'Остановить сервер';
|
||||||
@override String get newSession => 'Новая сессия';
|
|
||||||
@override String get minimize => 'Свернуть';
|
@override String get minimize => 'Свернуть';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingRu implements TranslationsCompanionRemo
|
|||||||
final TranslationsRu _root; // ignore: unused_field
|
final TranslationsRu _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get scan => 'Сканировать';
|
@override String get pairWithDesktop => 'Подключиться к компьютеру';
|
||||||
@override String get manual => 'Вручную';
|
@override String get discoveryDescription => 'Устройства в вашей сети с Plezy на том же аккаунте Plex появятся автоматически';
|
||||||
@override String get pairWithDesktop => 'Сопряжение с ПК';
|
|
||||||
@override String get enterSessionDetails => 'Введите данные сессии, показанные на десктопном устройстве';
|
|
||||||
@override String get hostAddressHint => '192.168.1.100:48632';
|
@override String get hostAddressHint => '192.168.1.100:48632';
|
||||||
@override String get sessionIdHint => 'Введите 8-символьный ID сессии';
|
|
||||||
@override String get pinHint => 'Введите 6-значный PIN';
|
|
||||||
@override String get connecting => 'Подключение...';
|
@override String get connecting => 'Подключение...';
|
||||||
@override String get tips => 'Советы';
|
@override String get searchingForDevices => 'Поиск устройств...';
|
||||||
@override String get tipDesktop => 'Откройте Plezy на десктопе и включите пульт управления в настройках или меню';
|
@override String get noDevicesFound => 'Устройства в вашей сети не найдены';
|
||||||
@override String get tipScan => 'Используйте вкладку «Сканировать» для быстрого сопряжения через QR-код на десктопе';
|
@override String get noDevicesHint => 'Убедитесь, что Plezy открыт на вашем компьютере и оба устройства находятся в одной сети WiFi';
|
||||||
@override String get tipWifi => 'Убедитесь, что оба устройства находятся в одной WiFi-сети';
|
@override String get availableDevices => 'Доступные устройства';
|
||||||
@override String get cameraPermissionRequired => 'Для сканирования QR-кодов требуется разрешение на камеру.\nПредоставьте доступ к камере в настройках устройства.';
|
@override String get manualConnection => 'Ручное подключение';
|
||||||
@override String cameraError({required Object error}) => 'Не удалось запустить камеру: ${error}';
|
@override String get cryptoInitFailed => 'Не удалось инициализировать безопасное соединение. Убедитесь, что вы вошли в аккаунт Plex.';
|
||||||
@override String get scanInstruction => 'Наведите камеру на QR-код, показанный на десктопе';
|
|
||||||
@override String get invalidQrCode => 'Недопустимый формат QR-кода';
|
|
||||||
@override String get validationHostRequired => 'Введите адрес хоста';
|
@override String get validationHostRequired => 'Введите адрес хоста';
|
||||||
@override String get validationHostFormat => 'Формат должен быть IP:порт (например, 192.168.1.100:48632)';
|
@override String get validationHostFormat => 'Формат должен быть IP:порт (например, 192.168.1.100:48632)';
|
||||||
@override String get validationSessionIdRequired => 'Введите ID сессии';
|
@override String get connectionTimedOut => 'Время подключения истекло. Убедитесь, что оба устройства находятся в одной сети.';
|
||||||
@override String get validationSessionIdLength => 'ID сессии должен содержать 8 символов';
|
@override String get sessionNotFound => 'Устройство не найдено. Убедитесь, что Plezy запущен на хосте.';
|
||||||
@override String get validationPinRequired => 'Введите PIN';
|
@override String get authFailed => 'Ошибка аутентификации. Убедитесь, что оба устройства используют один и тот же аккаунт Plex.';
|
||||||
@override String get validationPinLength => 'PIN должен содержать 6 цифр';
|
|
||||||
@override String get connectionTimedOut => 'Время подключения истекло. Проверьте ID сессии и PIN.';
|
|
||||||
@override String get sessionNotFound => 'Сессия не найдена. Проверьте ваши учётные данные.';
|
|
||||||
@override String failedToConnect({required Object error}) => 'Не удалось подключиться: ${error}';
|
@override String failedToConnect({required Object error}) => 'Не удалось подключиться: ${error}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1600,6 +1590,8 @@ extension on TranslationsRu {
|
|||||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||||
'settings.seekAndTiming' => 'Seek & Timing',
|
'settings.seekAndTiming' => 'Seek & Timing',
|
||||||
'settings.behavior' => 'Behavior',
|
'settings.behavior' => 'Behavior',
|
||||||
|
'settings.companionRemoteServer' => 'Сервер удалённого управления',
|
||||||
|
'settings.companionRemoteServerDescription' => 'Разрешить мобильным устройствам в сети управлять этим приложением',
|
||||||
'search.hint' => 'Поиск фильмов, сериалов, музыки...',
|
'search.hint' => 'Поиск фильмов, сериалов, музыки...',
|
||||||
'search.tryDifferentTerm' => 'Попробуйте другой запрос',
|
'search.tryDifferentTerm' => 'Попробуйте другой запрос',
|
||||||
'search.searchYourMedia' => 'Поиск в вашей медиатеке',
|
'search.searchYourMedia' => 'Поиск в вашей медиатеке',
|
||||||
@@ -1881,10 +1873,10 @@ extension on TranslationsRu {
|
|||||||
'libraries.groupings.seasons' => 'Сезоны',
|
'libraries.groupings.seasons' => 'Сезоны',
|
||||||
'libraries.groupings.episodes' => 'Эпизоды',
|
'libraries.groupings.episodes' => 'Эпизоды',
|
||||||
'libraries.groupings.folders' => 'Папки',
|
'libraries.groupings.folders' => 'Папки',
|
||||||
'about.title' => 'О приложении',
|
|
||||||
'about.openSourceLicenses' => 'Лицензии открытого ПО',
|
|
||||||
_ => null,
|
_ => null,
|
||||||
} ?? switch (path) {
|
} ?? switch (path) {
|
||||||
|
'about.title' => 'О приложении',
|
||||||
|
'about.openSourceLicenses' => 'Лицензии открытого ПО',
|
||||||
'about.versionLabel' => ({required Object version}) => 'Версия ${version}',
|
'about.versionLabel' => ({required Object version}) => 'Версия ${version}',
|
||||||
'about.appDescription' => 'Красивый клиент Plex на Flutter',
|
'about.appDescription' => 'Красивый клиент Plex на Flutter',
|
||||||
'about.viewLicensesDescription' => 'Просмотр лицензий сторонних библиотек',
|
'about.viewLicensesDescription' => 'Просмотр лицензий сторонних библиотек',
|
||||||
@@ -2076,45 +2068,33 @@ extension on TranslationsRu {
|
|||||||
'companionRemote.remoteControl' => 'Пульт управления',
|
'companionRemote.remoteControl' => 'Пульт управления',
|
||||||
'companionRemote.controlDesktop' => 'Управлять десктопным устройством',
|
'companionRemote.controlDesktop' => 'Управлять десктопным устройством',
|
||||||
'companionRemote.connectedTo' => ({required Object name}) => 'Подключено к ${name}',
|
'companionRemote.connectedTo' => ({required Object name}) => 'Подключено к ${name}',
|
||||||
'companionRemote.session.creatingSession' => 'Создание удалённой сессии...',
|
'companionRemote.session.startingServer' => 'Запуск удалённого сервера...',
|
||||||
'companionRemote.session.failedToCreate' => 'Не удалось создать удалённую сессию:',
|
'companionRemote.session.failedToCreate' => 'Не удалось запустить удалённый сервер:',
|
||||||
'companionRemote.session.noSession' => 'Нет доступных сессий',
|
|
||||||
'companionRemote.session.scanQrCode' => 'Сканировать QR-код',
|
|
||||||
'companionRemote.session.orEnterManually' => 'Или введите вручную',
|
|
||||||
'companionRemote.session.hostAddress' => 'Адрес хоста',
|
'companionRemote.session.hostAddress' => 'Адрес хоста',
|
||||||
'companionRemote.session.sessionId' => 'ID сессии',
|
|
||||||
'companionRemote.session.pin' => 'PIN',
|
|
||||||
'companionRemote.session.connected' => 'Подключено',
|
'companionRemote.session.connected' => 'Подключено',
|
||||||
'companionRemote.session.waitingForConnection' => 'Ожидание подключения...',
|
'companionRemote.session.serverRunning' => 'Удалённый сервер активен',
|
||||||
|
'companionRemote.session.serverStopped' => 'Удалённый сервер остановлен',
|
||||||
|
'companionRemote.session.serverRunningDescription' => 'Мобильные устройства в вашей сети могут обнаруживать это приложение и подключаться к нему',
|
||||||
|
'companionRemote.session.serverStoppedDescription' => 'Запустите сервер, чтобы разрешить подключение мобильных устройств',
|
||||||
'companionRemote.session.usePhoneToControl' => 'Используйте мобильное устройство для управления этим приложением',
|
'companionRemote.session.usePhoneToControl' => 'Используйте мобильное устройство для управления этим приложением',
|
||||||
'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} скопировано в буфер обмена',
|
'companionRemote.session.startServer' => 'Запустить сервер',
|
||||||
'companionRemote.session.copyToClipboard' => 'Скопировать в буфер обмена',
|
'companionRemote.session.stopServer' => 'Остановить сервер',
|
||||||
'companionRemote.session.newSession' => 'Новая сессия',
|
|
||||||
'companionRemote.session.minimize' => 'Свернуть',
|
'companionRemote.session.minimize' => 'Свернуть',
|
||||||
'companionRemote.pairing.scan' => 'Сканировать',
|
'companionRemote.pairing.pairWithDesktop' => 'Подключиться к компьютеру',
|
||||||
'companionRemote.pairing.manual' => 'Вручную',
|
'companionRemote.pairing.discoveryDescription' => 'Устройства в вашей сети с Plezy на том же аккаунте Plex появятся автоматически',
|
||||||
'companionRemote.pairing.pairWithDesktop' => 'Сопряжение с ПК',
|
|
||||||
'companionRemote.pairing.enterSessionDetails' => 'Введите данные сессии, показанные на десктопном устройстве',
|
|
||||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||||
'companionRemote.pairing.sessionIdHint' => 'Введите 8-символьный ID сессии',
|
|
||||||
'companionRemote.pairing.pinHint' => 'Введите 6-значный PIN',
|
|
||||||
'companionRemote.pairing.connecting' => 'Подключение...',
|
'companionRemote.pairing.connecting' => 'Подключение...',
|
||||||
'companionRemote.pairing.tips' => 'Советы',
|
'companionRemote.pairing.searchingForDevices' => 'Поиск устройств...',
|
||||||
'companionRemote.pairing.tipDesktop' => 'Откройте Plezy на десктопе и включите пульт управления в настройках или меню',
|
'companionRemote.pairing.noDevicesFound' => 'Устройства в вашей сети не найдены',
|
||||||
'companionRemote.pairing.tipScan' => 'Используйте вкладку «Сканировать» для быстрого сопряжения через QR-код на десктопе',
|
'companionRemote.pairing.noDevicesHint' => 'Убедитесь, что Plezy открыт на вашем компьютере и оба устройства находятся в одной сети WiFi',
|
||||||
'companionRemote.pairing.tipWifi' => 'Убедитесь, что оба устройства находятся в одной WiFi-сети',
|
'companionRemote.pairing.availableDevices' => 'Доступные устройства',
|
||||||
'companionRemote.pairing.cameraPermissionRequired' => 'Для сканирования QR-кодов требуется разрешение на камеру.\nПредоставьте доступ к камере в настройках устройства.',
|
'companionRemote.pairing.manualConnection' => 'Ручное подключение',
|
||||||
'companionRemote.pairing.cameraError' => ({required Object error}) => 'Не удалось запустить камеру: ${error}',
|
'companionRemote.pairing.cryptoInitFailed' => 'Не удалось инициализировать безопасное соединение. Убедитесь, что вы вошли в аккаунт Plex.',
|
||||||
'companionRemote.pairing.scanInstruction' => 'Наведите камеру на QR-код, показанный на десктопе',
|
|
||||||
'companionRemote.pairing.invalidQrCode' => 'Недопустимый формат QR-кода',
|
|
||||||
'companionRemote.pairing.validationHostRequired' => 'Введите адрес хоста',
|
'companionRemote.pairing.validationHostRequired' => 'Введите адрес хоста',
|
||||||
'companionRemote.pairing.validationHostFormat' => 'Формат должен быть IP:порт (например, 192.168.1.100:48632)',
|
'companionRemote.pairing.validationHostFormat' => 'Формат должен быть IP:порт (например, 192.168.1.100:48632)',
|
||||||
'companionRemote.pairing.validationSessionIdRequired' => 'Введите ID сессии',
|
'companionRemote.pairing.connectionTimedOut' => 'Время подключения истекло. Убедитесь, что оба устройства находятся в одной сети.',
|
||||||
'companionRemote.pairing.validationSessionIdLength' => 'ID сессии должен содержать 8 символов',
|
'companionRemote.pairing.sessionNotFound' => 'Устройство не найдено. Убедитесь, что Plezy запущен на хосте.',
|
||||||
'companionRemote.pairing.validationPinRequired' => 'Введите PIN',
|
'companionRemote.pairing.authFailed' => 'Ошибка аутентификации. Убедитесь, что оба устройства используют один и тот же аккаунт Plex.',
|
||||||
'companionRemote.pairing.validationPinLength' => 'PIN должен содержать 6 цифр',
|
|
||||||
'companionRemote.pairing.connectionTimedOut' => 'Время подключения истекло. Проверьте ID сессии и PIN.',
|
|
||||||
'companionRemote.pairing.sessionNotFound' => 'Сессия не найдена. Проверьте ваши учётные данные.',
|
|
||||||
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Не удалось подключиться: ${error}',
|
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Не удалось подключиться: ${error}',
|
||||||
'companionRemote.remote.disconnectConfirm' => 'Отключиться от удалённой сессии?',
|
'companionRemote.remote.disconnectConfirm' => 'Отключиться от удалённой сессии?',
|
||||||
'companionRemote.remote.reconnecting' => 'Переподключение...',
|
'companionRemote.remote.reconnecting' => 'Переподключение...',
|
||||||
|
|||||||
+46
-66
@@ -358,6 +358,8 @@ class _TranslationsSettingsSv implements TranslationsSettingsEn {
|
|||||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||||
@override String get seekAndTiming => 'Seek & Timing';
|
@override String get seekAndTiming => 'Seek & Timing';
|
||||||
@override String get behavior => 'Behavior';
|
@override String get behavior => 'Behavior';
|
||||||
|
@override String get companionRemoteServer => 'Companion Remote-server';
|
||||||
|
@override String get companionRemoteServerDescription => 'Tillåt mobila enheter i ditt nätverk att styra denna app';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: search
|
// Path: search
|
||||||
@@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionSv implements TranslationsCompanionRemo
|
|||||||
final TranslationsSv _root; // ignore: unused_field
|
final TranslationsSv _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get creatingSession => 'Skapar fjärrsession...';
|
@override String get startingServer => 'Startar fjärrserver...';
|
||||||
@override String get failedToCreate => 'Kunde inte skapa fjärrsession:';
|
@override String get failedToCreate => 'Kunde inte starta fjärrserver:';
|
||||||
@override String get noSession => 'Ingen session tillgänglig';
|
|
||||||
@override String get scanQrCode => 'Skanna QR-kod';
|
|
||||||
@override String get orEnterManually => 'Eller ange manuellt';
|
|
||||||
@override String get hostAddress => 'Värdadress';
|
@override String get hostAddress => 'Värdadress';
|
||||||
@override String get sessionId => 'Sessions-ID';
|
|
||||||
@override String get pin => 'PIN';
|
|
||||||
@override String get connected => 'Ansluten';
|
@override String get connected => 'Ansluten';
|
||||||
@override String get waitingForConnection => 'Väntar på anslutning...';
|
@override String get serverRunning => 'Fjärrserver aktiv';
|
||||||
|
@override String get serverStopped => 'Fjärrserver stoppad';
|
||||||
|
@override String get serverRunningDescription => 'Mobila enheter i ditt nätverk kan upptäcka och ansluta till denna app';
|
||||||
|
@override String get serverStoppedDescription => 'Starta servern för att tillåta mobila enheter att ansluta';
|
||||||
@override String get usePhoneToControl => 'Använd din mobila enhet för att styra denna app';
|
@override String get usePhoneToControl => 'Använd din mobila enhet för att styra denna app';
|
||||||
@override String copiedToClipboard({required Object label}) => '${label} kopierad till urklipp';
|
@override String get startServer => 'Starta server';
|
||||||
@override String get copyToClipboard => 'Kopiera till urklipp';
|
@override String get stopServer => 'Stoppa server';
|
||||||
@override String get newSession => 'Ny session';
|
|
||||||
@override String get minimize => 'Minimera';
|
@override String get minimize => 'Minimera';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingSv implements TranslationsCompanionRemo
|
|||||||
final TranslationsSv _root; // ignore: unused_field
|
final TranslationsSv _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get scan => 'Skanna';
|
@override String get pairWithDesktop => 'Anslut till dator';
|
||||||
@override String get manual => 'Manuell';
|
@override String get discoveryDescription => 'Enheter i ditt nätverk som kör Plezy med samma Plex-konto visas automatiskt';
|
||||||
@override String get pairWithDesktop => 'Parkoppla med dator';
|
|
||||||
@override String get enterSessionDetails => 'Ange sessionsuppgifterna som visas på din datorenhet';
|
|
||||||
@override String get hostAddressHint => '192.168.1.100:48632';
|
@override String get hostAddressHint => '192.168.1.100:48632';
|
||||||
@override String get sessionIdHint => 'Ange 8-teckens sessions-ID';
|
|
||||||
@override String get pinHint => 'Ange 6-siffrig PIN';
|
|
||||||
@override String get connecting => 'Ansluter...';
|
@override String get connecting => 'Ansluter...';
|
||||||
@override String get tips => 'Tips';
|
@override String get searchingForDevices => 'Söker efter enheter...';
|
||||||
@override String get tipDesktop => 'Öppna Plezy på din dator och aktivera Companion Remote via inställningar eller meny';
|
@override String get noDevicesFound => 'Inga enheter hittades i ditt nätverk';
|
||||||
@override String get tipScan => 'Använd fliken Skanna för att snabbt parkoppla genom att skanna QR-koden på din dator';
|
@override String get noDevicesHint => 'Se till att Plezy är öppet på din dator och att båda enheterna är på samma WiFi-nätverk';
|
||||||
@override String get tipWifi => 'Se till att båda enheterna är på samma WiFi-nätverk';
|
@override String get availableDevices => 'Tillgängliga enheter';
|
||||||
@override String get cameraPermissionRequired => 'Kamerabehörighet krävs för att skanna QR-koder.\nVänligen ge kameraåtkomst i enhetsinställningarna.';
|
@override String get manualConnection => 'Manuell anslutning';
|
||||||
@override String cameraError({required Object error}) => 'Kunde inte starta kameran: ${error}';
|
@override String get cryptoInitFailed => 'Kunde inte initiera säker anslutning. Se till att du är inloggad på ett Plex-konto.';
|
||||||
@override String get scanInstruction => 'Rikta kameran mot QR-koden som visas på din dator';
|
@override String get validationHostRequired => 'Ange värdadress';
|
||||||
@override String get invalidQrCode => 'Ogiltigt QR-kodformat';
|
|
||||||
@override String get validationHostRequired => 'Ange en värdadress';
|
|
||||||
@override String get validationHostFormat => 'Format måste vara IP:port (t.ex. 192.168.1.100:48632)';
|
@override String get validationHostFormat => 'Format måste vara IP:port (t.ex. 192.168.1.100:48632)';
|
||||||
@override String get validationSessionIdRequired => 'Ange ett sessions-ID';
|
@override String get connectionTimedOut => 'Anslutningen tog för lång tid. Se till att båda enheterna är på samma nätverk.';
|
||||||
@override String get validationSessionIdLength => 'Sessions-ID måste vara 8 tecken';
|
@override String get sessionNotFound => 'Kunde inte hitta enheten. Se till att Plezy körs på värden.';
|
||||||
@override String get validationPinRequired => 'Ange en PIN';
|
@override String get authFailed => 'Autentisering misslyckades. Se till att båda enheterna använder samma Plex-konto.';
|
||||||
@override String get validationPinLength => 'PIN måste vara 6 siffror';
|
|
||||||
@override String get connectionTimedOut => 'Anslutningen tog för lång tid. Kontrollera sessions-ID och PIN.';
|
|
||||||
@override String get sessionNotFound => 'Kunde inte hitta sessionen. Kontrollera dina uppgifter.';
|
|
||||||
@override String failedToConnect({required Object error}) => 'Kunde inte ansluta: ${error}';
|
@override String failedToConnect({required Object error}) => 'Kunde inte ansluta: ${error}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1600,6 +1590,8 @@ extension on TranslationsSv {
|
|||||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||||
'settings.seekAndTiming' => 'Seek & Timing',
|
'settings.seekAndTiming' => 'Seek & Timing',
|
||||||
'settings.behavior' => 'Behavior',
|
'settings.behavior' => 'Behavior',
|
||||||
|
'settings.companionRemoteServer' => 'Companion Remote-server',
|
||||||
|
'settings.companionRemoteServerDescription' => 'Tillåt mobila enheter i ditt nätverk att styra denna app',
|
||||||
'search.hint' => 'Sök filmer, serier, musik...',
|
'search.hint' => 'Sök filmer, serier, musik...',
|
||||||
'search.tryDifferentTerm' => 'Prova en annan sökterm',
|
'search.tryDifferentTerm' => 'Prova en annan sökterm',
|
||||||
'search.searchYourMedia' => 'Sök i dina media',
|
'search.searchYourMedia' => 'Sök i dina media',
|
||||||
@@ -1881,10 +1873,10 @@ extension on TranslationsSv {
|
|||||||
'libraries.groupings.seasons' => 'Säsonger',
|
'libraries.groupings.seasons' => 'Säsonger',
|
||||||
'libraries.groupings.episodes' => 'Avsnitt',
|
'libraries.groupings.episodes' => 'Avsnitt',
|
||||||
'libraries.groupings.folders' => 'Mappar',
|
'libraries.groupings.folders' => 'Mappar',
|
||||||
'about.title' => 'Om',
|
|
||||||
'about.openSourceLicenses' => 'Öppen källkod-licenser',
|
|
||||||
_ => null,
|
_ => null,
|
||||||
} ?? switch (path) {
|
} ?? switch (path) {
|
||||||
|
'about.title' => 'Om',
|
||||||
|
'about.openSourceLicenses' => 'Öppen källkod-licenser',
|
||||||
'about.versionLabel' => ({required Object version}) => 'Version ${version}',
|
'about.versionLabel' => ({required Object version}) => 'Version ${version}',
|
||||||
'about.appDescription' => 'En vacker Plex-klient för Flutter',
|
'about.appDescription' => 'En vacker Plex-klient för Flutter',
|
||||||
'about.viewLicensesDescription' => 'Visa licenser för tredjepartsbibliotek',
|
'about.viewLicensesDescription' => 'Visa licenser för tredjepartsbibliotek',
|
||||||
@@ -2076,45 +2068,33 @@ extension on TranslationsSv {
|
|||||||
'companionRemote.remoteControl' => 'Fjärrkontroll',
|
'companionRemote.remoteControl' => 'Fjärrkontroll',
|
||||||
'companionRemote.controlDesktop' => 'Styr en datorenhet',
|
'companionRemote.controlDesktop' => 'Styr en datorenhet',
|
||||||
'companionRemote.connectedTo' => ({required Object name}) => 'Ansluten till ${name}',
|
'companionRemote.connectedTo' => ({required Object name}) => 'Ansluten till ${name}',
|
||||||
'companionRemote.session.creatingSession' => 'Skapar fjärrsession...',
|
'companionRemote.session.startingServer' => 'Startar fjärrserver...',
|
||||||
'companionRemote.session.failedToCreate' => 'Kunde inte skapa fjärrsession:',
|
'companionRemote.session.failedToCreate' => 'Kunde inte starta fjärrserver:',
|
||||||
'companionRemote.session.noSession' => 'Ingen session tillgänglig',
|
|
||||||
'companionRemote.session.scanQrCode' => 'Skanna QR-kod',
|
|
||||||
'companionRemote.session.orEnterManually' => 'Eller ange manuellt',
|
|
||||||
'companionRemote.session.hostAddress' => 'Värdadress',
|
'companionRemote.session.hostAddress' => 'Värdadress',
|
||||||
'companionRemote.session.sessionId' => 'Sessions-ID',
|
|
||||||
'companionRemote.session.pin' => 'PIN',
|
|
||||||
'companionRemote.session.connected' => 'Ansluten',
|
'companionRemote.session.connected' => 'Ansluten',
|
||||||
'companionRemote.session.waitingForConnection' => 'Väntar på anslutning...',
|
'companionRemote.session.serverRunning' => 'Fjärrserver aktiv',
|
||||||
|
'companionRemote.session.serverStopped' => 'Fjärrserver stoppad',
|
||||||
|
'companionRemote.session.serverRunningDescription' => 'Mobila enheter i ditt nätverk kan upptäcka och ansluta till denna app',
|
||||||
|
'companionRemote.session.serverStoppedDescription' => 'Starta servern för att tillåta mobila enheter att ansluta',
|
||||||
'companionRemote.session.usePhoneToControl' => 'Använd din mobila enhet för att styra denna app',
|
'companionRemote.session.usePhoneToControl' => 'Använd din mobila enhet för att styra denna app',
|
||||||
'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label} kopierad till urklipp',
|
'companionRemote.session.startServer' => 'Starta server',
|
||||||
'companionRemote.session.copyToClipboard' => 'Kopiera till urklipp',
|
'companionRemote.session.stopServer' => 'Stoppa server',
|
||||||
'companionRemote.session.newSession' => 'Ny session',
|
|
||||||
'companionRemote.session.minimize' => 'Minimera',
|
'companionRemote.session.minimize' => 'Minimera',
|
||||||
'companionRemote.pairing.scan' => 'Skanna',
|
'companionRemote.pairing.pairWithDesktop' => 'Anslut till dator',
|
||||||
'companionRemote.pairing.manual' => 'Manuell',
|
'companionRemote.pairing.discoveryDescription' => 'Enheter i ditt nätverk som kör Plezy med samma Plex-konto visas automatiskt',
|
||||||
'companionRemote.pairing.pairWithDesktop' => 'Parkoppla med dator',
|
|
||||||
'companionRemote.pairing.enterSessionDetails' => 'Ange sessionsuppgifterna som visas på din datorenhet',
|
|
||||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||||
'companionRemote.pairing.sessionIdHint' => 'Ange 8-teckens sessions-ID',
|
|
||||||
'companionRemote.pairing.pinHint' => 'Ange 6-siffrig PIN',
|
|
||||||
'companionRemote.pairing.connecting' => 'Ansluter...',
|
'companionRemote.pairing.connecting' => 'Ansluter...',
|
||||||
'companionRemote.pairing.tips' => 'Tips',
|
'companionRemote.pairing.searchingForDevices' => 'Söker efter enheter...',
|
||||||
'companionRemote.pairing.tipDesktop' => 'Öppna Plezy på din dator och aktivera Companion Remote via inställningar eller meny',
|
'companionRemote.pairing.noDevicesFound' => 'Inga enheter hittades i ditt nätverk',
|
||||||
'companionRemote.pairing.tipScan' => 'Använd fliken Skanna för att snabbt parkoppla genom att skanna QR-koden på din dator',
|
'companionRemote.pairing.noDevicesHint' => 'Se till att Plezy är öppet på din dator och att båda enheterna är på samma WiFi-nätverk',
|
||||||
'companionRemote.pairing.tipWifi' => 'Se till att båda enheterna är på samma WiFi-nätverk',
|
'companionRemote.pairing.availableDevices' => 'Tillgängliga enheter',
|
||||||
'companionRemote.pairing.cameraPermissionRequired' => 'Kamerabehörighet krävs för att skanna QR-koder.\nVänligen ge kameraåtkomst i enhetsinställningarna.',
|
'companionRemote.pairing.manualConnection' => 'Manuell anslutning',
|
||||||
'companionRemote.pairing.cameraError' => ({required Object error}) => 'Kunde inte starta kameran: ${error}',
|
'companionRemote.pairing.cryptoInitFailed' => 'Kunde inte initiera säker anslutning. Se till att du är inloggad på ett Plex-konto.',
|
||||||
'companionRemote.pairing.scanInstruction' => 'Rikta kameran mot QR-koden som visas på din dator',
|
'companionRemote.pairing.validationHostRequired' => 'Ange värdadress',
|
||||||
'companionRemote.pairing.invalidQrCode' => 'Ogiltigt QR-kodformat',
|
|
||||||
'companionRemote.pairing.validationHostRequired' => 'Ange en värdadress',
|
|
||||||
'companionRemote.pairing.validationHostFormat' => 'Format måste vara IP:port (t.ex. 192.168.1.100:48632)',
|
'companionRemote.pairing.validationHostFormat' => 'Format måste vara IP:port (t.ex. 192.168.1.100:48632)',
|
||||||
'companionRemote.pairing.validationSessionIdRequired' => 'Ange ett sessions-ID',
|
'companionRemote.pairing.connectionTimedOut' => 'Anslutningen tog för lång tid. Se till att båda enheterna är på samma nätverk.',
|
||||||
'companionRemote.pairing.validationSessionIdLength' => 'Sessions-ID måste vara 8 tecken',
|
'companionRemote.pairing.sessionNotFound' => 'Kunde inte hitta enheten. Se till att Plezy körs på värden.',
|
||||||
'companionRemote.pairing.validationPinRequired' => 'Ange en PIN',
|
'companionRemote.pairing.authFailed' => 'Autentisering misslyckades. Se till att båda enheterna använder samma Plex-konto.',
|
||||||
'companionRemote.pairing.validationPinLength' => 'PIN måste vara 6 siffror',
|
|
||||||
'companionRemote.pairing.connectionTimedOut' => 'Anslutningen tog för lång tid. Kontrollera sessions-ID och PIN.',
|
|
||||||
'companionRemote.pairing.sessionNotFound' => 'Kunde inte hitta sessionen. Kontrollera dina uppgifter.',
|
|
||||||
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Kunde inte ansluta: ${error}',
|
'companionRemote.pairing.failedToConnect' => ({required Object error}) => 'Kunde inte ansluta: ${error}',
|
||||||
'companionRemote.remote.disconnectConfirm' => 'Vill du koppla från fjärrsessionen?',
|
'companionRemote.remote.disconnectConfirm' => 'Vill du koppla från fjärrsessionen?',
|
||||||
'companionRemote.remote.reconnecting' => 'Återansluter...',
|
'companionRemote.remote.reconnecting' => 'Återansluter...',
|
||||||
|
|||||||
+48
-68
@@ -358,6 +358,8 @@ class _TranslationsSettingsZh implements TranslationsSettingsEn {
|
|||||||
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
@override String get subtitlesAndConfig => 'Subtitles & Configuration';
|
||||||
@override String get seekAndTiming => 'Seek & Timing';
|
@override String get seekAndTiming => 'Seek & Timing';
|
||||||
@override String get behavior => 'Behavior';
|
@override String get behavior => 'Behavior';
|
||||||
|
@override String get companionRemoteServer => '配套遥控服务器';
|
||||||
|
@override String get companionRemoteServerDescription => '允许网络上的移动设备控制此应用';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path: search
|
// Path: search
|
||||||
@@ -1274,20 +1276,17 @@ class _TranslationsCompanionRemoteSessionZh implements TranslationsCompanionRemo
|
|||||||
final TranslationsZh _root; // ignore: unused_field
|
final TranslationsZh _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get creatingSession => '正在创建远程会话...';
|
@override String get startingServer => '正在启动远程服务器...';
|
||||||
@override String get failedToCreate => '创建远程会话失败:';
|
@override String get failedToCreate => '启动远程服务器失败:';
|
||||||
@override String get noSession => '没有可用的会话';
|
|
||||||
@override String get scanQrCode => '扫描 QR 码';
|
|
||||||
@override String get orEnterManually => '或手动输入';
|
|
||||||
@override String get hostAddress => '主机地址';
|
@override String get hostAddress => '主机地址';
|
||||||
@override String get sessionId => '会话 ID';
|
|
||||||
@override String get pin => 'PIN';
|
|
||||||
@override String get connected => '已连接';
|
@override String get connected => '已连接';
|
||||||
@override String get waitingForConnection => '等待连接中...';
|
@override String get serverRunning => '远程服务器已启动';
|
||||||
|
@override String get serverStopped => '远程服务器已停止';
|
||||||
|
@override String get serverRunningDescription => '网络上的移动设备可以发现并连接到此应用';
|
||||||
|
@override String get serverStoppedDescription => '启动服务器以允许移动设备连接';
|
||||||
@override String get usePhoneToControl => '使用移动设备控制此应用';
|
@override String get usePhoneToControl => '使用移动设备控制此应用';
|
||||||
@override String copiedToClipboard({required Object label}) => '${label}已复制到剪贴板';
|
@override String get startServer => '启动服务器';
|
||||||
@override String get copyToClipboard => '复制到剪贴板';
|
@override String get stopServer => '停止服务器';
|
||||||
@override String get newSession => '新建会话';
|
|
||||||
@override String get minimize => '最小化';
|
@override String get minimize => '最小化';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1298,30 +1297,21 @@ class _TranslationsCompanionRemotePairingZh implements TranslationsCompanionRemo
|
|||||||
final TranslationsZh _root; // ignore: unused_field
|
final TranslationsZh _root; // ignore: unused_field
|
||||||
|
|
||||||
// Translations
|
// Translations
|
||||||
@override String get scan => '扫描';
|
@override String get pairWithDesktop => '连接到桌面';
|
||||||
@override String get manual => '手动';
|
@override String get discoveryDescription => '网络上使用相同Plex账户运行Plezy的设备将自动显示';
|
||||||
@override String get pairWithDesktop => '与桌面配对';
|
|
||||||
@override String get enterSessionDetails => '输入桌面设备上显示的会话信息';
|
|
||||||
@override String get hostAddressHint => '192.168.1.100:48632';
|
@override String get hostAddressHint => '192.168.1.100:48632';
|
||||||
@override String get sessionIdHint => '输入8位会话 ID';
|
@override String get connecting => '正在连接...';
|
||||||
@override String get pinHint => '输入6位 PIN';
|
@override String get searchingForDevices => '正在搜索设备...';
|
||||||
@override String get connecting => '连接中...';
|
@override String get noDevicesFound => '未在网络上找到设备';
|
||||||
@override String get tips => '提示';
|
@override String get noDevicesHint => '请确保桌面上已打开Plezy,且两台设备在同一WiFi网络上';
|
||||||
@override String get tipDesktop => '在桌面上打开 Plezy,并从设置或菜单中启用 Companion Remote';
|
@override String get availableDevices => '可用设备';
|
||||||
@override String get tipScan => '使用扫描选项卡扫描桌面上的 QR 码以快速配对';
|
@override String get manualConnection => '手动连接';
|
||||||
@override String get tipWifi => '请确保两台设备连接到同一个 WiFi 网络';
|
@override String get cryptoInitFailed => '无法初始化安全连接。请确保已登录Plex账户。';
|
||||||
@override String get cameraPermissionRequired => '扫描 QR 码需要相机权限。\n请在设备设置中授予相机访问权限。';
|
|
||||||
@override String cameraError({required Object error}) => '无法启动相机:${error}';
|
|
||||||
@override String get scanInstruction => '将相机对准桌面上显示的 QR 码';
|
|
||||||
@override String get invalidQrCode => '无效的 QR 码格式';
|
|
||||||
@override String get validationHostRequired => '请输入主机地址';
|
@override String get validationHostRequired => '请输入主机地址';
|
||||||
@override String get validationHostFormat => '格式必须为 IP:端口(例如 192.168.1.100:48632)';
|
@override String get validationHostFormat => '格式必须为IP:端口(例如 192.168.1.100:48632)';
|
||||||
@override String get validationSessionIdRequired => '请输入会话 ID';
|
@override String get connectionTimedOut => '连接超时。请确保两台设备在同一网络上。';
|
||||||
@override String get validationSessionIdLength => '会话 ID 必须为8个字符';
|
@override String get sessionNotFound => '未找到设备。请确保Plezy正在主机上运行。';
|
||||||
@override String get validationPinRequired => '请输入 PIN';
|
@override String get authFailed => '认证失败。请确保两台设备使用相同的Plex账户。';
|
||||||
@override String get validationPinLength => 'PIN 必须为6位数字';
|
|
||||||
@override String get connectionTimedOut => '连接超时。请检查会话 ID 和 PIN。';
|
|
||||||
@override String get sessionNotFound => '找不到会话。请检查您的凭据。';
|
|
||||||
@override String failedToConnect({required Object error}) => '连接失败:${error}';
|
@override String failedToConnect({required Object error}) => '连接失败:${error}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1600,6 +1590,8 @@ extension on TranslationsZh {
|
|||||||
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
'settings.subtitlesAndConfig' => 'Subtitles & Configuration',
|
||||||
'settings.seekAndTiming' => 'Seek & Timing',
|
'settings.seekAndTiming' => 'Seek & Timing',
|
||||||
'settings.behavior' => 'Behavior',
|
'settings.behavior' => 'Behavior',
|
||||||
|
'settings.companionRemoteServer' => '配套遥控服务器',
|
||||||
|
'settings.companionRemoteServerDescription' => '允许网络上的移动设备控制此应用',
|
||||||
'search.hint' => '搜索电影、系列、音乐...',
|
'search.hint' => '搜索电影、系列、音乐...',
|
||||||
'search.tryDifferentTerm' => '尝试不同的搜索词',
|
'search.tryDifferentTerm' => '尝试不同的搜索词',
|
||||||
'search.searchYourMedia' => '搜索媒体',
|
'search.searchYourMedia' => '搜索媒体',
|
||||||
@@ -1881,10 +1873,10 @@ extension on TranslationsZh {
|
|||||||
'libraries.groupings.seasons' => '季',
|
'libraries.groupings.seasons' => '季',
|
||||||
'libraries.groupings.episodes' => '集',
|
'libraries.groupings.episodes' => '集',
|
||||||
'libraries.groupings.folders' => '文件夹',
|
'libraries.groupings.folders' => '文件夹',
|
||||||
'about.title' => '关于',
|
|
||||||
'about.openSourceLicenses' => '开源许可证',
|
|
||||||
_ => null,
|
_ => null,
|
||||||
} ?? switch (path) {
|
} ?? switch (path) {
|
||||||
|
'about.title' => '关于',
|
||||||
|
'about.openSourceLicenses' => '开源许可证',
|
||||||
'about.versionLabel' => ({required Object version}) => '版本 ${version}',
|
'about.versionLabel' => ({required Object version}) => '版本 ${version}',
|
||||||
'about.appDescription' => '一款精美的 Flutter Plex 客户端',
|
'about.appDescription' => '一款精美的 Flutter Plex 客户端',
|
||||||
'about.viewLicensesDescription' => '查看第三方库的许可证',
|
'about.viewLicensesDescription' => '查看第三方库的许可证',
|
||||||
@@ -2076,45 +2068,33 @@ extension on TranslationsZh {
|
|||||||
'companionRemote.remoteControl' => '远程控制',
|
'companionRemote.remoteControl' => '远程控制',
|
||||||
'companionRemote.controlDesktop' => '控制桌面设备',
|
'companionRemote.controlDesktop' => '控制桌面设备',
|
||||||
'companionRemote.connectedTo' => ({required Object name}) => '已连接到 ${name}',
|
'companionRemote.connectedTo' => ({required Object name}) => '已连接到 ${name}',
|
||||||
'companionRemote.session.creatingSession' => '正在创建远程会话...',
|
'companionRemote.session.startingServer' => '正在启动远程服务器...',
|
||||||
'companionRemote.session.failedToCreate' => '创建远程会话失败:',
|
'companionRemote.session.failedToCreate' => '启动远程服务器失败:',
|
||||||
'companionRemote.session.noSession' => '没有可用的会话',
|
|
||||||
'companionRemote.session.scanQrCode' => '扫描 QR 码',
|
|
||||||
'companionRemote.session.orEnterManually' => '或手动输入',
|
|
||||||
'companionRemote.session.hostAddress' => '主机地址',
|
'companionRemote.session.hostAddress' => '主机地址',
|
||||||
'companionRemote.session.sessionId' => '会话 ID',
|
|
||||||
'companionRemote.session.pin' => 'PIN',
|
|
||||||
'companionRemote.session.connected' => '已连接',
|
'companionRemote.session.connected' => '已连接',
|
||||||
'companionRemote.session.waitingForConnection' => '等待连接中...',
|
'companionRemote.session.serverRunning' => '远程服务器已启动',
|
||||||
|
'companionRemote.session.serverStopped' => '远程服务器已停止',
|
||||||
|
'companionRemote.session.serverRunningDescription' => '网络上的移动设备可以发现并连接到此应用',
|
||||||
|
'companionRemote.session.serverStoppedDescription' => '启动服务器以允许移动设备连接',
|
||||||
'companionRemote.session.usePhoneToControl' => '使用移动设备控制此应用',
|
'companionRemote.session.usePhoneToControl' => '使用移动设备控制此应用',
|
||||||
'companionRemote.session.copiedToClipboard' => ({required Object label}) => '${label}已复制到剪贴板',
|
'companionRemote.session.startServer' => '启动服务器',
|
||||||
'companionRemote.session.copyToClipboard' => '复制到剪贴板',
|
'companionRemote.session.stopServer' => '停止服务器',
|
||||||
'companionRemote.session.newSession' => '新建会话',
|
|
||||||
'companionRemote.session.minimize' => '最小化',
|
'companionRemote.session.minimize' => '最小化',
|
||||||
'companionRemote.pairing.scan' => '扫描',
|
'companionRemote.pairing.pairWithDesktop' => '连接到桌面',
|
||||||
'companionRemote.pairing.manual' => '手动',
|
'companionRemote.pairing.discoveryDescription' => '网络上使用相同Plex账户运行Plezy的设备将自动显示',
|
||||||
'companionRemote.pairing.pairWithDesktop' => '与桌面配对',
|
|
||||||
'companionRemote.pairing.enterSessionDetails' => '输入桌面设备上显示的会话信息',
|
|
||||||
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
'companionRemote.pairing.hostAddressHint' => '192.168.1.100:48632',
|
||||||
'companionRemote.pairing.sessionIdHint' => '输入8位会话 ID',
|
'companionRemote.pairing.connecting' => '正在连接...',
|
||||||
'companionRemote.pairing.pinHint' => '输入6位 PIN',
|
'companionRemote.pairing.searchingForDevices' => '正在搜索设备...',
|
||||||
'companionRemote.pairing.connecting' => '连接中...',
|
'companionRemote.pairing.noDevicesFound' => '未在网络上找到设备',
|
||||||
'companionRemote.pairing.tips' => '提示',
|
'companionRemote.pairing.noDevicesHint' => '请确保桌面上已打开Plezy,且两台设备在同一WiFi网络上',
|
||||||
'companionRemote.pairing.tipDesktop' => '在桌面上打开 Plezy,并从设置或菜单中启用 Companion Remote',
|
'companionRemote.pairing.availableDevices' => '可用设备',
|
||||||
'companionRemote.pairing.tipScan' => '使用扫描选项卡扫描桌面上的 QR 码以快速配对',
|
'companionRemote.pairing.manualConnection' => '手动连接',
|
||||||
'companionRemote.pairing.tipWifi' => '请确保两台设备连接到同一个 WiFi 网络',
|
'companionRemote.pairing.cryptoInitFailed' => '无法初始化安全连接。请确保已登录Plex账户。',
|
||||||
'companionRemote.pairing.cameraPermissionRequired' => '扫描 QR 码需要相机权限。\n请在设备设置中授予相机访问权限。',
|
|
||||||
'companionRemote.pairing.cameraError' => ({required Object error}) => '无法启动相机:${error}',
|
|
||||||
'companionRemote.pairing.scanInstruction' => '将相机对准桌面上显示的 QR 码',
|
|
||||||
'companionRemote.pairing.invalidQrCode' => '无效的 QR 码格式',
|
|
||||||
'companionRemote.pairing.validationHostRequired' => '请输入主机地址',
|
'companionRemote.pairing.validationHostRequired' => '请输入主机地址',
|
||||||
'companionRemote.pairing.validationHostFormat' => '格式必须为 IP:端口(例如 192.168.1.100:48632)',
|
'companionRemote.pairing.validationHostFormat' => '格式必须为IP:端口(例如 192.168.1.100:48632)',
|
||||||
'companionRemote.pairing.validationSessionIdRequired' => '请输入会话 ID',
|
'companionRemote.pairing.connectionTimedOut' => '连接超时。请确保两台设备在同一网络上。',
|
||||||
'companionRemote.pairing.validationSessionIdLength' => '会话 ID 必须为8个字符',
|
'companionRemote.pairing.sessionNotFound' => '未找到设备。请确保Plezy正在主机上运行。',
|
||||||
'companionRemote.pairing.validationPinRequired' => '请输入 PIN',
|
'companionRemote.pairing.authFailed' => '认证失败。请确保两台设备使用相同的Plex账户。',
|
||||||
'companionRemote.pairing.validationPinLength' => 'PIN 必须为6位数字',
|
|
||||||
'companionRemote.pairing.connectionTimedOut' => '连接超时。请检查会话 ID 和 PIN。',
|
|
||||||
'companionRemote.pairing.sessionNotFound' => '找不到会话。请检查您的凭据。',
|
|
||||||
'companionRemote.pairing.failedToConnect' => ({required Object error}) => '连接失败:${error}',
|
'companionRemote.pairing.failedToConnect' => ({required Object error}) => '连接失败:${error}',
|
||||||
'companionRemote.remote.disconnectConfirm' => '是否要断开远程会话的连接?',
|
'companionRemote.remote.disconnectConfirm' => '是否要断开远程会话的连接?',
|
||||||
'companionRemote.remote.reconnecting' => '重新连接中...',
|
'companionRemote.remote.reconnecting' => '重新连接中...',
|
||||||
|
|||||||
+23
-33
@@ -238,7 +238,9 @@
|
|||||||
"player": "Player",
|
"player": "Player",
|
||||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||||
"seekAndTiming": "Seek & Timing",
|
"seekAndTiming": "Seek & Timing",
|
||||||
"behavior": "Behavior"
|
"behavior": "Behavior",
|
||||||
|
"companionRemoteServer": "Companion Remote-server",
|
||||||
|
"companionRemoteServerDescription": "Tillåt mobila enheter i ditt nätverk att styra denna app"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"hint": "Sök filmer, serier, musik...",
|
"hint": "Sök filmer, serier, musik...",
|
||||||
@@ -778,47 +780,35 @@
|
|||||||
"controlDesktop": "Styr en datorenhet",
|
"controlDesktop": "Styr en datorenhet",
|
||||||
"connectedTo": "Ansluten till ${name}",
|
"connectedTo": "Ansluten till ${name}",
|
||||||
"session": {
|
"session": {
|
||||||
"creatingSession": "Skapar fjärrsession...",
|
"startingServer": "Startar fjärrserver...",
|
||||||
"failedToCreate": "Kunde inte skapa fjärrsession:",
|
"failedToCreate": "Kunde inte starta fjärrserver:",
|
||||||
"noSession": "Ingen session tillgänglig",
|
|
||||||
"scanQrCode": "Skanna QR-kod",
|
|
||||||
"orEnterManually": "Eller ange manuellt",
|
|
||||||
"hostAddress": "Värdadress",
|
"hostAddress": "Värdadress",
|
||||||
"sessionId": "Sessions-ID",
|
|
||||||
"pin": "PIN",
|
|
||||||
"connected": "Ansluten",
|
"connected": "Ansluten",
|
||||||
"waitingForConnection": "Väntar på anslutning...",
|
"serverRunning": "Fjärrserver aktiv",
|
||||||
|
"serverStopped": "Fjärrserver stoppad",
|
||||||
|
"serverRunningDescription": "Mobila enheter i ditt nätverk kan upptäcka och ansluta till denna app",
|
||||||
|
"serverStoppedDescription": "Starta servern för att tillåta mobila enheter att ansluta",
|
||||||
"usePhoneToControl": "Använd din mobila enhet för att styra denna app",
|
"usePhoneToControl": "Använd din mobila enhet för att styra denna app",
|
||||||
"copiedToClipboard": "${label} kopierad till urklipp",
|
"startServer": "Starta server",
|
||||||
"copyToClipboard": "Kopiera till urklipp",
|
"stopServer": "Stoppa server",
|
||||||
"newSession": "Ny session",
|
|
||||||
"minimize": "Minimera"
|
"minimize": "Minimera"
|
||||||
},
|
},
|
||||||
"pairing": {
|
"pairing": {
|
||||||
"scan": "Skanna",
|
"pairWithDesktop": "Anslut till dator",
|
||||||
"manual": "Manuell",
|
"discoveryDescription": "Enheter i ditt nätverk som kör Plezy med samma Plex-konto visas automatiskt",
|
||||||
"pairWithDesktop": "Parkoppla med dator",
|
|
||||||
"enterSessionDetails": "Ange sessionsuppgifterna som visas på din datorenhet",
|
|
||||||
"hostAddressHint": "192.168.1.100:48632",
|
"hostAddressHint": "192.168.1.100:48632",
|
||||||
"sessionIdHint": "Ange 8-teckens sessions-ID",
|
|
||||||
"pinHint": "Ange 6-siffrig PIN",
|
|
||||||
"connecting": "Ansluter...",
|
"connecting": "Ansluter...",
|
||||||
"tips": "Tips",
|
"searchingForDevices": "Söker efter enheter...",
|
||||||
"tipDesktop": "Öppna Plezy på din dator och aktivera Companion Remote via inställningar eller meny",
|
"noDevicesFound": "Inga enheter hittades i ditt nätverk",
|
||||||
"tipScan": "Använd fliken Skanna för att snabbt parkoppla genom att skanna QR-koden på din dator",
|
"noDevicesHint": "Se till att Plezy är öppet på din dator och att båda enheterna är på samma WiFi-nätverk",
|
||||||
"tipWifi": "Se till att båda enheterna är på samma WiFi-nätverk",
|
"availableDevices": "Tillgängliga enheter",
|
||||||
"cameraPermissionRequired": "Kamerabehörighet krävs för att skanna QR-koder.\nVänligen ge kameraåtkomst i enhetsinställningarna.",
|
"manualConnection": "Manuell anslutning",
|
||||||
"cameraError": "Kunde inte starta kameran: ${error}",
|
"cryptoInitFailed": "Kunde inte initiera säker anslutning. Se till att du är inloggad på ett Plex-konto.",
|
||||||
"scanInstruction": "Rikta kameran mot QR-koden som visas på din dator",
|
"validationHostRequired": "Ange värdadress",
|
||||||
"invalidQrCode": "Ogiltigt QR-kodformat",
|
|
||||||
"validationHostRequired": "Ange en värdadress",
|
|
||||||
"validationHostFormat": "Format måste vara IP:port (t.ex. 192.168.1.100:48632)",
|
"validationHostFormat": "Format måste vara IP:port (t.ex. 192.168.1.100:48632)",
|
||||||
"validationSessionIdRequired": "Ange ett sessions-ID",
|
"connectionTimedOut": "Anslutningen tog för lång tid. Se till att båda enheterna är på samma nätverk.",
|
||||||
"validationSessionIdLength": "Sessions-ID måste vara 8 tecken",
|
"sessionNotFound": "Kunde inte hitta enheten. Se till att Plezy körs på värden.",
|
||||||
"validationPinRequired": "Ange en PIN",
|
"authFailed": "Autentisering misslyckades. Se till att båda enheterna använder samma Plex-konto.",
|
||||||
"validationPinLength": "PIN måste vara 6 siffror",
|
|
||||||
"connectionTimedOut": "Anslutningen tog för lång tid. Kontrollera sessions-ID och PIN.",
|
|
||||||
"sessionNotFound": "Kunde inte hitta sessionen. Kontrollera dina uppgifter.",
|
|
||||||
"failedToConnect": "Kunde inte ansluta: ${error}"
|
"failedToConnect": "Kunde inte ansluta: ${error}"
|
||||||
},
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
|
|||||||
+24
-34
@@ -238,7 +238,9 @@
|
|||||||
"player": "Player",
|
"player": "Player",
|
||||||
"subtitlesAndConfig": "Subtitles & Configuration",
|
"subtitlesAndConfig": "Subtitles & Configuration",
|
||||||
"seekAndTiming": "Seek & Timing",
|
"seekAndTiming": "Seek & Timing",
|
||||||
"behavior": "Behavior"
|
"behavior": "Behavior",
|
||||||
|
"companionRemoteServer": "配套遥控服务器",
|
||||||
|
"companionRemoteServerDescription": "允许网络上的移动设备控制此应用"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"hint": "搜索电影、系列、音乐...",
|
"hint": "搜索电影、系列、音乐...",
|
||||||
@@ -778,47 +780,35 @@
|
|||||||
"controlDesktop": "控制桌面设备",
|
"controlDesktop": "控制桌面设备",
|
||||||
"connectedTo": "已连接到 ${name}",
|
"connectedTo": "已连接到 ${name}",
|
||||||
"session": {
|
"session": {
|
||||||
"creatingSession": "正在创建远程会话...",
|
"startingServer": "正在启动远程服务器...",
|
||||||
"failedToCreate": "创建远程会话失败:",
|
"failedToCreate": "启动远程服务器失败:",
|
||||||
"noSession": "没有可用的会话",
|
|
||||||
"scanQrCode": "扫描 QR 码",
|
|
||||||
"orEnterManually": "或手动输入",
|
|
||||||
"hostAddress": "主机地址",
|
"hostAddress": "主机地址",
|
||||||
"sessionId": "会话 ID",
|
|
||||||
"pin": "PIN",
|
|
||||||
"connected": "已连接",
|
"connected": "已连接",
|
||||||
"waitingForConnection": "等待连接中...",
|
"serverRunning": "远程服务器已启动",
|
||||||
|
"serverStopped": "远程服务器已停止",
|
||||||
|
"serverRunningDescription": "网络上的移动设备可以发现并连接到此应用",
|
||||||
|
"serverStoppedDescription": "启动服务器以允许移动设备连接",
|
||||||
"usePhoneToControl": "使用移动设备控制此应用",
|
"usePhoneToControl": "使用移动设备控制此应用",
|
||||||
"copiedToClipboard": "${label}已复制到剪贴板",
|
"startServer": "启动服务器",
|
||||||
"copyToClipboard": "复制到剪贴板",
|
"stopServer": "停止服务器",
|
||||||
"newSession": "新建会话",
|
|
||||||
"minimize": "最小化"
|
"minimize": "最小化"
|
||||||
},
|
},
|
||||||
"pairing": {
|
"pairing": {
|
||||||
"scan": "扫描",
|
"pairWithDesktop": "连接到桌面",
|
||||||
"manual": "手动",
|
"discoveryDescription": "网络上使用相同Plex账户运行Plezy的设备将自动显示",
|
||||||
"pairWithDesktop": "与桌面配对",
|
|
||||||
"enterSessionDetails": "输入桌面设备上显示的会话信息",
|
|
||||||
"hostAddressHint": "192.168.1.100:48632",
|
"hostAddressHint": "192.168.1.100:48632",
|
||||||
"sessionIdHint": "输入8位会话 ID",
|
"connecting": "正在连接...",
|
||||||
"pinHint": "输入6位 PIN",
|
"searchingForDevices": "正在搜索设备...",
|
||||||
"connecting": "连接中...",
|
"noDevicesFound": "未在网络上找到设备",
|
||||||
"tips": "提示",
|
"noDevicesHint": "请确保桌面上已打开Plezy,且两台设备在同一WiFi网络上",
|
||||||
"tipDesktop": "在桌面上打开 Plezy,并从设置或菜单中启用 Companion Remote",
|
"availableDevices": "可用设备",
|
||||||
"tipScan": "使用扫描选项卡扫描桌面上的 QR 码以快速配对",
|
"manualConnection": "手动连接",
|
||||||
"tipWifi": "请确保两台设备连接到同一个 WiFi 网络",
|
"cryptoInitFailed": "无法初始化安全连接。请确保已登录Plex账户。",
|
||||||
"cameraPermissionRequired": "扫描 QR 码需要相机权限。\n请在设备设置中授予相机访问权限。",
|
|
||||||
"cameraError": "无法启动相机:${error}",
|
|
||||||
"scanInstruction": "将相机对准桌面上显示的 QR 码",
|
|
||||||
"invalidQrCode": "无效的 QR 码格式",
|
|
||||||
"validationHostRequired": "请输入主机地址",
|
"validationHostRequired": "请输入主机地址",
|
||||||
"validationHostFormat": "格式必须为 IP:端口(例如 192.168.1.100:48632)",
|
"validationHostFormat": "格式必须为IP:端口(例如 192.168.1.100:48632)",
|
||||||
"validationSessionIdRequired": "请输入会话 ID",
|
"connectionTimedOut": "连接超时。请确保两台设备在同一网络上。",
|
||||||
"validationSessionIdLength": "会话 ID 必须为8个字符",
|
"sessionNotFound": "未找到设备。请确保Plezy正在主机上运行。",
|
||||||
"validationPinRequired": "请输入 PIN",
|
"authFailed": "认证失败。请确保两台设备使用相同的Plex账户。",
|
||||||
"validationPinLength": "PIN 必须为6位数字",
|
|
||||||
"connectionTimedOut": "连接超时。请检查会话 ID 和 PIN。",
|
|
||||||
"sessionNotFound": "找不到会话。请检查您的凭据。",
|
|
||||||
"failedToConnect": "连接失败:${error}"
|
"failedToConnect": "连接失败:${error}"
|
||||||
},
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
|
|||||||
@@ -56,8 +56,6 @@ class RemoteDevice {
|
|||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class RemoteSession {
|
class RemoteSession {
|
||||||
final String sessionId;
|
|
||||||
final String pin;
|
|
||||||
@JsonKey(unknownEnumValue: RemoteSessionRole.remote)
|
@JsonKey(unknownEnumValue: RemoteSessionRole.remote)
|
||||||
final RemoteSessionRole role;
|
final RemoteSessionRole role;
|
||||||
@JsonKey(unknownEnumValue: RemoteSessionStatus.disconnected)
|
@JsonKey(unknownEnumValue: RemoteSessionStatus.disconnected)
|
||||||
@@ -67,8 +65,6 @@ class RemoteSession {
|
|||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
|
|
||||||
RemoteSession({
|
RemoteSession({
|
||||||
required this.sessionId,
|
|
||||||
required this.pin,
|
|
||||||
required this.role,
|
required this.role,
|
||||||
this.status = RemoteSessionStatus.disconnected,
|
this.status = RemoteSessionStatus.disconnected,
|
||||||
this.connectedDevice,
|
this.connectedDevice,
|
||||||
@@ -85,8 +81,6 @@ class RemoteSession {
|
|||||||
Map<String, dynamic> toJson() => _$RemoteSessionToJson(this);
|
Map<String, dynamic> toJson() => _$RemoteSessionToJson(this);
|
||||||
|
|
||||||
RemoteSession copyWith({
|
RemoteSession copyWith({
|
||||||
String? sessionId,
|
|
||||||
String? pin,
|
|
||||||
RemoteSessionRole? role,
|
RemoteSessionRole? role,
|
||||||
RemoteSessionStatus? status,
|
RemoteSessionStatus? status,
|
||||||
RemoteDevice? connectedDevice,
|
RemoteDevice? connectedDevice,
|
||||||
@@ -96,8 +90,6 @@ class RemoteSession {
|
|||||||
bool clearErrorMessage = false,
|
bool clearErrorMessage = false,
|
||||||
}) {
|
}) {
|
||||||
return RemoteSession(
|
return RemoteSession(
|
||||||
sessionId: sessionId ?? this.sessionId,
|
|
||||||
pin: pin ?? this.pin,
|
|
||||||
role: role ?? this.role,
|
role: role ?? this.role,
|
||||||
status: status ?? this.status,
|
status: status ?? this.status,
|
||||||
connectedDevice: clearConnectedDevice ? null : (connectedDevice ?? this.connectedDevice),
|
connectedDevice: clearConnectedDevice ? null : (connectedDevice ?? this.connectedDevice),
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ Map<String, dynamic> _$RemoteDeviceToJson(RemoteDevice instance) =>
|
|||||||
|
|
||||||
RemoteSession _$RemoteSessionFromJson(Map<String, dynamic> json) =>
|
RemoteSession _$RemoteSessionFromJson(Map<String, dynamic> json) =>
|
||||||
RemoteSession(
|
RemoteSession(
|
||||||
sessionId: json['sessionId'] as String,
|
|
||||||
pin: json['pin'] as String,
|
|
||||||
role: $enumDecode(
|
role: $enumDecode(
|
||||||
_$RemoteSessionRoleEnumMap,
|
_$RemoteSessionRoleEnumMap,
|
||||||
json['role'],
|
json['role'],
|
||||||
@@ -56,8 +54,6 @@ RemoteSession _$RemoteSessionFromJson(Map<String, dynamic> json) =>
|
|||||||
|
|
||||||
Map<String, dynamic> _$RemoteSessionToJson(RemoteSession instance) =>
|
Map<String, dynamic> _$RemoteSessionToJson(RemoteSession instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'sessionId': instance.sessionId,
|
|
||||||
'pin': instance.pin,
|
|
||||||
'role': _$RemoteSessionRoleEnumMap[instance.role]!,
|
'role': _$RemoteSessionRoleEnumMap[instance.role]!,
|
||||||
'status': _$RemoteSessionStatusEnumMap[instance.status]!,
|
'status': _$RemoteSessionStatusEnumMap[instance.status]!,
|
||||||
'connectedDevice': instance.connectedDevice,
|
'connectedDevice': instance.connectedDevice,
|
||||||
|
|||||||
@@ -6,14 +6,21 @@ import 'package:device_info_plus/device_info_plus.dart';
|
|||||||
|
|
||||||
import '../models/companion_remote/remote_command.dart';
|
import '../models/companion_remote/remote_command.dart';
|
||||||
import '../models/companion_remote/remote_session.dart';
|
import '../models/companion_remote/remote_session.dart';
|
||||||
|
import '../models/plex_home.dart';
|
||||||
import '../services/companion_remote/companion_remote_peer_service.dart';
|
import '../services/companion_remote/companion_remote_peer_service.dart';
|
||||||
|
import '../services/companion_remote/lan_discovery_service.dart';
|
||||||
|
import '../services/companion_remote/remote_auth_service.dart';
|
||||||
|
import '../services/storage_service.dart';
|
||||||
import '../utils/app_logger.dart';
|
import '../utils/app_logger.dart';
|
||||||
|
|
||||||
|
export '../services/companion_remote/lan_discovery_service.dart' show DiscoveredHost;
|
||||||
|
|
||||||
typedef CommandReceivedCallback = void Function(RemoteCommand command);
|
typedef CommandReceivedCallback = void Function(RemoteCommand command);
|
||||||
|
|
||||||
class CompanionRemoteProvider with ChangeNotifier {
|
class CompanionRemoteProvider with ChangeNotifier {
|
||||||
RemoteSession? _session;
|
RemoteSession? _session;
|
||||||
CompanionRemotePeerService? _peerService;
|
CompanionRemotePeerService? _peerService;
|
||||||
|
LanDiscoveryService? _discoveryService;
|
||||||
String _deviceName = 'Unknown Device';
|
String _deviceName = 'Unknown Device';
|
||||||
String _platform = 'unknown';
|
String _platform = 'unknown';
|
||||||
bool _isPlayerActive = false;
|
bool _isPlayerActive = false;
|
||||||
@@ -23,9 +30,17 @@ class CompanionRemoteProvider with ChangeNotifier {
|
|||||||
Timer? _reconnectTimer;
|
Timer? _reconnectTimer;
|
||||||
int _reconnectAttempts = 0;
|
int _reconnectAttempts = 0;
|
||||||
bool _intentionalDisconnect = false;
|
bool _intentionalDisconnect = false;
|
||||||
String? _lastSessionId;
|
|
||||||
String? _lastPin;
|
// Reconnection context (only hostAddresses and hostClientId are connection-specific)
|
||||||
String? _lastHostAddress;
|
List<String>? _lastHostAddresses;
|
||||||
|
String? _lastHostClientId;
|
||||||
|
|
||||||
|
// Crypto context (derived in memory, never persisted)
|
||||||
|
List<int>? _homeSecret;
|
||||||
|
List<int>? _discoveryKey;
|
||||||
|
String? _clientIdentifier;
|
||||||
|
String? _userUUID;
|
||||||
|
List<String>? _homeUserUUIDs;
|
||||||
|
|
||||||
int get reconnectAttempts => _reconnectAttempts;
|
int get reconnectAttempts => _reconnectAttempts;
|
||||||
|
|
||||||
@@ -43,10 +58,9 @@ class CompanionRemoteProvider with ChangeNotifier {
|
|||||||
bool get isConnected => _session?.isConnected ?? false;
|
bool get isConnected => _session?.isConnected ?? false;
|
||||||
RemoteSession? get session => _session;
|
RemoteSession? get session => _session;
|
||||||
RemoteSessionStatus get status => _session?.status ?? RemoteSessionStatus.disconnected;
|
RemoteSessionStatus get status => _session?.status ?? RemoteSessionStatus.disconnected;
|
||||||
String? get sessionId => _session?.sessionId;
|
|
||||||
String? get pin => _session?.pin;
|
|
||||||
RemoteDevice? get connectedDevice => _session?.connectedDevice;
|
RemoteDevice? get connectedDevice => _session?.connectedDevice;
|
||||||
bool get isPlayerActive => _isPlayerActive;
|
bool get isPlayerActive => _isPlayerActive;
|
||||||
|
bool get isHostServerRunning => _peerService?.isServerRunning ?? false;
|
||||||
|
|
||||||
CompanionRemoteProvider() {
|
CompanionRemoteProvider() {
|
||||||
_initializeDeviceInfo();
|
_initializeDeviceInfo();
|
||||||
@@ -86,6 +100,219 @@ class CompanionRemoteProvider with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initialize crypto context from Plex home data.
|
||||||
|
/// Must be called before startHostServer or connectToDiscoveredHost.
|
||||||
|
Future<bool> initializeCrypto(PlexHome? home, StorageService storage) async {
|
||||||
|
if (home == null || home.adminUser == null) {
|
||||||
|
appLogger.w('CompanionRemote: Cannot init crypto — no home data');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final auth = RemoteAuthService.instance;
|
||||||
|
_homeSecret = await auth.deriveHomeSecretFromHome(home);
|
||||||
|
_discoveryKey = await auth.deriveDiscoveryKey(_homeSecret!);
|
||||||
|
_clientIdentifier = storage.getClientIdentifier();
|
||||||
|
_userUUID = storage.getCurrentUserUUID();
|
||||||
|
_homeUserUUIDs = home.users.map((u) => u.uuid).toList();
|
||||||
|
|
||||||
|
appLogger.d('CompanionRemote: Crypto context initialized');
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
appLogger.e('CompanionRemote: Failed to init crypto', error: e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isCryptoReady => _homeSecret != null && _discoveryKey != null && _clientIdentifier != null;
|
||||||
|
|
||||||
|
/// Convenience: ensure crypto is initialized using providers from context.
|
||||||
|
/// Returns true if crypto is ready (already initialized or just initialized).
|
||||||
|
Future<bool> ensureCryptoReady(PlexHome? home) async {
|
||||||
|
if (isCryptoReady) return true;
|
||||||
|
final storage = await StorageService.getInstance();
|
||||||
|
return initializeCrypto(home, storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Host Server ──
|
||||||
|
|
||||||
|
/// Start the host server and begin LAN broadcasting. Idempotent.
|
||||||
|
Future<void> startHostServer() async {
|
||||||
|
if (_peerService?.isServerRunning == true) return;
|
||||||
|
if (!isCryptoReady) {
|
||||||
|
appLogger.w('CompanionRemote: Cannot start host — crypto not initialized');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
appLogger.d('CompanionRemote: Starting host server');
|
||||||
|
|
||||||
|
_peerService ??= CompanionRemotePeerService();
|
||||||
|
_setupPeerServiceListeners();
|
||||||
|
|
||||||
|
try {
|
||||||
|
final result = await _peerService!.createSession(
|
||||||
|
_deviceName,
|
||||||
|
_platform,
|
||||||
|
_homeSecret!,
|
||||||
|
_clientIdentifier!,
|
||||||
|
_homeUserUUIDs!,
|
||||||
|
);
|
||||||
|
|
||||||
|
_session = RemoteSession(
|
||||||
|
role: RemoteSessionRole.host,
|
||||||
|
status: RemoteSessionStatus.connected,
|
||||||
|
);
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
// Start LAN discovery broadcasting
|
||||||
|
_discoveryService ??= LanDiscoveryService();
|
||||||
|
final localIps = result.addresses.map((a) => a.split(':').first).toList();
|
||||||
|
await _discoveryService!.startBroadcasting(
|
||||||
|
discoveryKey: _discoveryKey!,
|
||||||
|
deviceName: _deviceName,
|
||||||
|
platform: _platform,
|
||||||
|
clientId: _clientIdentifier!,
|
||||||
|
wsPort: result.port,
|
||||||
|
ips: localIps,
|
||||||
|
);
|
||||||
|
|
||||||
|
appLogger.d('CompanionRemote: Host server running, broadcasting on LAN');
|
||||||
|
} catch (e) {
|
||||||
|
appLogger.e('CompanionRemote: Failed to start host server', error: e);
|
||||||
|
_session = RemoteSession(
|
||||||
|
role: RemoteSessionRole.host,
|
||||||
|
status: RemoteSessionStatus.error,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop the host server and LAN broadcasting.
|
||||||
|
Future<void> stopHostServer() async {
|
||||||
|
_intentionalDisconnect = true;
|
||||||
|
_discoveryService?.stopBroadcasting();
|
||||||
|
|
||||||
|
if (_peerService != null) {
|
||||||
|
await _peerService!.disconnect();
|
||||||
|
_peerService = null;
|
||||||
|
}
|
||||||
|
_cleanupSubscriptions();
|
||||||
|
|
||||||
|
_session = null;
|
||||||
|
_isPlayerActive = false;
|
||||||
|
_intentionalDisconnect = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Client: Discovery ──
|
||||||
|
|
||||||
|
/// Start listening for host beacons. Returns a stream of discovered hosts.
|
||||||
|
Stream<List<DiscoveredHost>>? discoverHosts() {
|
||||||
|
if (!isCryptoReady) {
|
||||||
|
appLogger.w('CompanionRemote: Cannot discover — crypto not initialized');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_discoveryService ??= LanDiscoveryService();
|
||||||
|
return _discoveryService!.startListening(discoveryKey: _discoveryKey!);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop listening for host beacons.
|
||||||
|
void stopDiscovery() {
|
||||||
|
_discoveryService?.stopListening();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connect to a discovered host as a remote client.
|
||||||
|
Future<void> connectToDiscoveredHost(DiscoveredHost host) async {
|
||||||
|
if (!isCryptoReady) {
|
||||||
|
throw StateError('Crypto not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
await leaveSession();
|
||||||
|
|
||||||
|
_lastHostAddresses = host.addresses;
|
||||||
|
_lastHostClientId = host.clientId;
|
||||||
|
|
||||||
|
appLogger.d('CompanionRemote: Connecting to ${host.name} at ${host.addresses}');
|
||||||
|
|
||||||
|
_peerService = CompanionRemotePeerService();
|
||||||
|
_setupPeerServiceListeners();
|
||||||
|
|
||||||
|
_session = RemoteSession(
|
||||||
|
role: RemoteSessionRole.remote,
|
||||||
|
status: RemoteSessionStatus.connecting,
|
||||||
|
);
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
try {
|
||||||
|
final winner = await _peerService!.joinSessionRacing(
|
||||||
|
_deviceName,
|
||||||
|
_platform,
|
||||||
|
host.addresses,
|
||||||
|
_homeSecret!,
|
||||||
|
host.clientId,
|
||||||
|
_userUUID!,
|
||||||
|
_clientIdentifier!,
|
||||||
|
);
|
||||||
|
_lastHostAddresses = [winner];
|
||||||
|
|
||||||
|
_session = _session?.copyWith(status: RemoteSessionStatus.connected);
|
||||||
|
notifyListeners();
|
||||||
|
appLogger.d('CompanionRemote: Connected to ${host.name} via $winner');
|
||||||
|
} catch (e) {
|
||||||
|
appLogger.e('CompanionRemote: Failed to connect to host', error: e);
|
||||||
|
_session = _session?.copyWith(status: RemoteSessionStatus.error, errorMessage: e.toString());
|
||||||
|
notifyListeners();
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connect to a host by manual IP:port entry.
|
||||||
|
Future<void> connectToManualHost(String hostAddress) async {
|
||||||
|
if (!isCryptoReady) {
|
||||||
|
throw StateError('Crypto not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
await leaveSession();
|
||||||
|
|
||||||
|
_lastHostAddresses = [hostAddress];
|
||||||
|
_lastHostClientId = '';
|
||||||
|
|
||||||
|
appLogger.d('CompanionRemote: Connecting to manual host $hostAddress');
|
||||||
|
|
||||||
|
_peerService = CompanionRemotePeerService();
|
||||||
|
_setupPeerServiceListeners();
|
||||||
|
|
||||||
|
_session = RemoteSession(
|
||||||
|
role: RemoteSessionRole.remote,
|
||||||
|
status: RemoteSessionStatus.connecting,
|
||||||
|
);
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await _peerService!.joinSession(
|
||||||
|
_deviceName,
|
||||||
|
_platform,
|
||||||
|
hostAddress,
|
||||||
|
_homeSecret!,
|
||||||
|
'', // Empty hostClientId — accept any host in same home
|
||||||
|
_userUUID!,
|
||||||
|
_clientIdentifier!,
|
||||||
|
);
|
||||||
|
|
||||||
|
_session = _session?.copyWith(status: RemoteSessionStatus.connected);
|
||||||
|
notifyListeners();
|
||||||
|
} catch (e) {
|
||||||
|
appLogger.e('CompanionRemote: Failed to connect to manual host', error: e);
|
||||||
|
_session = _session?.copyWith(status: RemoteSessionStatus.error, errorMessage: e.toString());
|
||||||
|
notifyListeners();
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Peer service listeners ──
|
||||||
|
|
||||||
void _setupPeerServiceListeners() {
|
void _setupPeerServiceListeners() {
|
||||||
_commandSubscription = _peerService!.onCommandReceived.listen(
|
_commandSubscription = _peerService!.onCommandReceived.listen(
|
||||||
(command) {
|
(command) {
|
||||||
@@ -118,7 +345,6 @@ class CompanionRemoteProvider with ChangeNotifier {
|
|||||||
_session = _session?.copyWith(status: RemoteSessionStatus.disconnected, clearConnectedDevice: true);
|
_session = _session?.copyWith(status: RemoteSessionStatus.disconnected, clearConnectedDevice: true);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} else if (isHost) {
|
} else if (isHost) {
|
||||||
// Host keeps the server running — the client will reconnect on its own
|
|
||||||
_session = _session?.copyWith(
|
_session = _session?.copyWith(
|
||||||
status: RemoteSessionStatus.reconnecting,
|
status: RemoteSessionStatus.reconnecting,
|
||||||
clearConnectedDevice: true,
|
clearConnectedDevice: true,
|
||||||
@@ -183,93 +409,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
|||||||
_statusSubscription = null;
|
_statusSubscription = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<({String sessionId, String pin, List<String> addresses})> createSession() async {
|
// ── Commands ──
|
||||||
await leaveSession();
|
|
||||||
|
|
||||||
appLogger.d('CompanionRemote: Creating session as host');
|
|
||||||
|
|
||||||
_peerService = CompanionRemotePeerService();
|
|
||||||
_setupPeerServiceListeners();
|
|
||||||
|
|
||||||
try {
|
|
||||||
final result = await _peerService!.createSession(_deviceName, _platform);
|
|
||||||
|
|
||||||
_session = RemoteSession(
|
|
||||||
sessionId: result.sessionId,
|
|
||||||
pin: result.pin,
|
|
||||||
role: RemoteSessionRole.host,
|
|
||||||
status: RemoteSessionStatus.connected,
|
|
||||||
);
|
|
||||||
|
|
||||||
notifyListeners();
|
|
||||||
appLogger.d(
|
|
||||||
'CompanionRemote: Session created - ID: ${result.sessionId}, PIN: ${result.pin}, Addresses: ${result.addresses}',
|
|
||||||
);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (e) {
|
|
||||||
appLogger.e('CompanionRemote: Failed to create session', error: e);
|
|
||||||
_session = RemoteSession(
|
|
||||||
sessionId: '',
|
|
||||||
pin: '',
|
|
||||||
role: RemoteSessionRole.host,
|
|
||||||
status: RemoteSessionStatus.error,
|
|
||||||
errorMessage: e.toString(),
|
|
||||||
);
|
|
||||||
notifyListeners();
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> joinSession(String sessionId, String pin, String hostAddress) async {
|
|
||||||
await joinSessionMulti(sessionId, pin, [hostAddress]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> joinSessionMulti(String sessionId, String pin, List<String> hostAddresses) async {
|
|
||||||
if (hostAddresses.isEmpty) {
|
|
||||||
throw ArgumentError('hostAddresses must not be empty');
|
|
||||||
}
|
|
||||||
|
|
||||||
await leaveSession();
|
|
||||||
|
|
||||||
_lastSessionId = sessionId;
|
|
||||||
_lastPin = pin;
|
|
||||||
// Store first address as fallback for reconnection; will be updated with winner
|
|
||||||
_lastHostAddress = hostAddresses.first;
|
|
||||||
|
|
||||||
appLogger.d('CompanionRemote: Joining session - ID: $sessionId, Hosts: $hostAddresses');
|
|
||||||
|
|
||||||
_peerService = CompanionRemotePeerService();
|
|
||||||
_setupPeerServiceListeners();
|
|
||||||
|
|
||||||
_session = RemoteSession(
|
|
||||||
sessionId: sessionId,
|
|
||||||
pin: pin,
|
|
||||||
role: RemoteSessionRole.remote,
|
|
||||||
status: RemoteSessionStatus.connecting,
|
|
||||||
);
|
|
||||||
notifyListeners();
|
|
||||||
|
|
||||||
try {
|
|
||||||
final winner = await _peerService!.joinSessionRacing(
|
|
||||||
sessionId,
|
|
||||||
pin,
|
|
||||||
_deviceName,
|
|
||||||
_platform,
|
|
||||||
hostAddresses,
|
|
||||||
);
|
|
||||||
_lastHostAddress = winner;
|
|
||||||
|
|
||||||
_session = _session?.copyWith(status: RemoteSessionStatus.connected);
|
|
||||||
notifyListeners();
|
|
||||||
appLogger.d('CompanionRemote: Successfully joined session via $winner');
|
|
||||||
} catch (e) {
|
|
||||||
appLogger.e('CompanionRemote: Failed to join session', error: e);
|
|
||||||
_session = _session?.copyWith(status: RemoteSessionStatus.error, errorMessage: e.toString());
|
|
||||||
notifyListeners();
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendCommand(RemoteCommandType type, {Map<String, dynamic>? data}) {
|
void sendCommand(RemoteCommandType type, {Map<String, dynamic>? data}) {
|
||||||
if (_peerService == null || !isConnected) {
|
if (_peerService == null || !isConnected) {
|
||||||
@@ -281,6 +421,8 @@ class CompanionRemoteProvider with ChangeNotifier {
|
|||||||
_peerService!.sendCommand(RemoteCommand(type: type, data: data));
|
_peerService!.sendCommand(RemoteCommand(type: type, data: data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Reconnection ──
|
||||||
|
|
||||||
void _scheduleReconnect() {
|
void _scheduleReconnect() {
|
||||||
if (_reconnectAttempts >= _maxReconnectAttempts) {
|
if (_reconnectAttempts >= _maxReconnectAttempts) {
|
||||||
appLogger.w('CompanionRemote: Max reconnect attempts reached');
|
appLogger.w('CompanionRemote: Max reconnect attempts reached');
|
||||||
@@ -293,7 +435,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final delay = Duration(seconds: 1 << _reconnectAttempts); // 1s, 2s, 4s, 8s, 16s
|
final delay = Duration(seconds: 1 << _reconnectAttempts);
|
||||||
_reconnectAttempts++;
|
_reconnectAttempts++;
|
||||||
appLogger.d('CompanionRemote: Reconnect attempt $_reconnectAttempts in ${delay.inSeconds}s');
|
appLogger.d('CompanionRemote: Reconnect attempt $_reconnectAttempts in ${delay.inSeconds}s');
|
||||||
|
|
||||||
@@ -302,8 +444,8 @@ class CompanionRemoteProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _attemptReconnect() async {
|
Future<void> _attemptReconnect() async {
|
||||||
if (_lastSessionId == null || _lastPin == null || _lastHostAddress == null) {
|
if (_lastHostAddresses == null || !isCryptoReady) {
|
||||||
appLogger.w('CompanionRemote: No stored credentials for reconnect');
|
appLogger.w('CompanionRemote: No stored context for reconnect');
|
||||||
_session = _session?.copyWith(status: RemoteSessionStatus.error, errorMessage: 'Connection lost');
|
_session = _session?.copyWith(status: RemoteSessionStatus.error, errorMessage: 'Connection lost');
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
return;
|
return;
|
||||||
@@ -311,7 +453,6 @@ class CompanionRemoteProvider with ChangeNotifier {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
appLogger.d('CompanionRemote: Attempting reconnect...');
|
appLogger.d('CompanionRemote: Attempting reconnect...');
|
||||||
// Clean up old peer service without triggering intentional disconnect
|
|
||||||
_cleanupSubscriptions();
|
_cleanupSubscriptions();
|
||||||
try {
|
try {
|
||||||
await _peerService?.disconnect();
|
await _peerService?.disconnect();
|
||||||
@@ -320,7 +461,15 @@ class CompanionRemoteProvider with ChangeNotifier {
|
|||||||
_setupPeerServiceListeners();
|
_setupPeerServiceListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
await _peerService!.joinSession(_lastSessionId!, _lastPin!, _deviceName, _platform, _lastHostAddress!);
|
await _peerService!.joinSession(
|
||||||
|
_deviceName,
|
||||||
|
_platform,
|
||||||
|
_lastHostAddresses!.first,
|
||||||
|
_homeSecret!,
|
||||||
|
_lastHostClientId ?? '',
|
||||||
|
_userUUID!,
|
||||||
|
_clientIdentifier!,
|
||||||
|
);
|
||||||
|
|
||||||
_session = _session?.copyWith(status: RemoteSessionStatus.connected, clearErrorMessage: true);
|
_session = _session?.copyWith(status: RemoteSessionStatus.connected, clearErrorMessage: true);
|
||||||
_reconnectAttempts = 0;
|
_reconnectAttempts = 0;
|
||||||
@@ -334,14 +483,12 @@ class CompanionRemoteProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Immediately retry reconnection, skipping the backoff wait
|
|
||||||
void retryReconnectNow() {
|
void retryReconnectNow() {
|
||||||
_reconnectTimer?.cancel();
|
_reconnectTimer?.cancel();
|
||||||
_reconnectAttempts = 0;
|
_reconnectAttempts = 0;
|
||||||
_attemptReconnect();
|
_attemptReconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cancel ongoing reconnection attempts
|
|
||||||
void cancelReconnect() {
|
void cancelReconnect() {
|
||||||
_reconnectTimer?.cancel();
|
_reconnectTimer?.cancel();
|
||||||
_reconnectAttempts = 0;
|
_reconnectAttempts = 0;
|
||||||
@@ -354,7 +501,8 @@ class CompanionRemoteProvider with ChangeNotifier {
|
|||||||
_reconnectTimer?.cancel();
|
_reconnectTimer?.cancel();
|
||||||
_reconnectAttempts = 0;
|
_reconnectAttempts = 0;
|
||||||
|
|
||||||
if (_peerService != null) {
|
// Don't stop the host server when leaving — only stop discovery listening
|
||||||
|
if (_peerService != null && !isHost) {
|
||||||
appLogger.d('CompanionRemote: Leaving session');
|
appLogger.d('CompanionRemote: Leaving session');
|
||||||
await _peerService!.disconnect();
|
await _peerService!.disconnect();
|
||||||
_peerService = null;
|
_peerService = null;
|
||||||
@@ -362,7 +510,9 @@ class CompanionRemoteProvider with ChangeNotifier {
|
|||||||
|
|
||||||
_cleanupSubscriptions();
|
_cleanupSubscriptions();
|
||||||
|
|
||||||
|
if (!isHost) {
|
||||||
_session = null;
|
_session = null;
|
||||||
|
}
|
||||||
_isPlayerActive = false;
|
_isPlayerActive = false;
|
||||||
_intentionalDisconnect = false;
|
_intentionalDisconnect = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -371,7 +521,9 @@ class CompanionRemoteProvider with ChangeNotifier {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_reconnectTimer?.cancel();
|
_reconnectTimer?.cancel();
|
||||||
leaveSession();
|
_discoveryService?.dispose();
|
||||||
|
_peerService?.dispose();
|
||||||
|
RemoteAuthService.instance.clearCache();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import 'dart:io';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../../i18n/strings.g.dart';
|
import '../../i18n/strings.g.dart';
|
||||||
import '../../providers/companion_remote_provider.dart';
|
import '../../providers/companion_remote_provider.dart';
|
||||||
|
import '../../providers/user_profile_provider.dart';
|
||||||
import '../../utils/app_logger.dart';
|
import '../../utils/app_logger.dart';
|
||||||
|
|
||||||
class PairingScreen extends StatefulWidget {
|
class PairingScreen extends StatefulWidget {
|
||||||
@@ -18,58 +17,97 @@ class PairingScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _PairingScreenState extends State<PairingScreen> {
|
class _PairingScreenState extends State<PairingScreen> {
|
||||||
final _hostAddressController = TextEditingController();
|
final _hostAddressController = TextEditingController();
|
||||||
final _sessionIdController = TextEditingController();
|
|
||||||
final _pinController = TextEditingController();
|
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
bool _isConnecting = false;
|
bool _isConnecting = false;
|
||||||
String? _errorMessage;
|
String? _errorMessage;
|
||||||
int _selectedTab = 0;
|
bool _showManualEntry = false;
|
||||||
|
bool _cryptoReady = false;
|
||||||
|
|
||||||
// QR scanner state
|
StreamSubscription<List<DiscoveredHost>>? _discoverySubscription;
|
||||||
MobileScannerController? _scannerController;
|
List<DiscoveredHost> _hosts = [];
|
||||||
String? _lastScannedCode;
|
bool _isSearching = true;
|
||||||
bool _hasCamera = false;
|
Timer? _searchTimeout;
|
||||||
|
|
||||||
bool get _canScan => (Platform.isAndroid || Platform.isIOS) && _hasCamera;
|
|
||||||
|
|
||||||
// Tab indices: scannable gets Scan (0) + Manual (1), otherwise Manual (0)
|
|
||||||
int get _scanTabIndex => _canScan ? 0 : -1;
|
|
||||||
int get _manualTabIndex => _canScan ? 1 : 0;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_checkCamera();
|
_initCryptoAndDiscover();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _checkCamera() async {
|
Future<void> _initCryptoAndDiscover() async {
|
||||||
if (Platform.isIOS) {
|
final provider = context.read<CompanionRemoteProvider>();
|
||||||
setState(() => _hasCamera = true);
|
final home = context.read<UserProfileProvider>().home;
|
||||||
return;
|
await provider.ensureCryptoReady(home);
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
if (provider.isCryptoReady) {
|
||||||
|
setState(() => _cryptoReady = true);
|
||||||
|
_startDiscovery();
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_cryptoReady = false;
|
||||||
|
_isSearching = false;
|
||||||
|
_errorMessage = t.companionRemote.pairing.cryptoInitFailed;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (!Platform.isAndroid) return;
|
|
||||||
try {
|
|
||||||
const channel = MethodChannel('com.plezy/theme');
|
|
||||||
final result = await channel.invokeMethod<bool>('hasCamera');
|
|
||||||
if (mounted) setState(() => _hasCamera = result ?? false);
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('Camera check failed: $e');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _startDiscovery() {
|
||||||
|
final provider = context.read<CompanionRemoteProvider>();
|
||||||
|
final stream = provider.discoverHosts();
|
||||||
|
if (stream == null) return;
|
||||||
|
|
||||||
|
_discoverySubscription = stream.listen((hosts) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_hosts = hosts;
|
||||||
|
if (hosts.isNotEmpty) _isSearching = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show "no devices found" after 10 seconds of no results
|
||||||
|
_searchTimeout = Timer(const Duration(seconds: 10), () {
|
||||||
|
if (mounted && _hosts.isEmpty) {
|
||||||
|
setState(() => _isSearching = false);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_hostAddressController.dispose();
|
_hostAddressController.dispose();
|
||||||
_sessionIdController.dispose();
|
_discoverySubscription?.cancel();
|
||||||
_pinController.dispose();
|
_searchTimeout?.cancel();
|
||||||
_scannerController?.dispose();
|
context.read<CompanionRemoteProvider>().stopDiscovery();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _connect() async {
|
Future<void> _connectToHost(DiscoveredHost host) async {
|
||||||
if (!_formKey.currentState!.validate()) {
|
setState(() {
|
||||||
return;
|
_isConnecting = true;
|
||||||
|
_errorMessage = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
final provider = context.read<CompanionRemoteProvider>();
|
||||||
|
await provider.connectToDiscoveredHost(host);
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
appLogger.e('Failed to connect to host', error: e);
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_isConnecting = false;
|
||||||
|
_errorMessage = _parseErrorMessage(e.toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _connectManual() async {
|
||||||
|
if (!_formKey.currentState!.validate()) return;
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_isConnecting = true;
|
_isConnecting = true;
|
||||||
@@ -78,17 +116,13 @@ class _PairingScreenState extends State<PairingScreen> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final provider = context.read<CompanionRemoteProvider>();
|
final provider = context.read<CompanionRemoteProvider>();
|
||||||
await provider.joinSession(
|
await provider.connectToManualHost(_hostAddressController.text.trim());
|
||||||
_sessionIdController.text.trim().toUpperCase(),
|
|
||||||
_pinController.text.trim(),
|
|
||||||
_hostAddressController.text.trim(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
appLogger.e('Failed to join remote session', error: e);
|
appLogger.e('Failed to connect to manual host', error: e);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_isConnecting = false;
|
_isConnecting = false;
|
||||||
@@ -102,73 +136,22 @@ class _PairingScreenState extends State<PairingScreen> {
|
|||||||
return t.companionRemote.pairing.connectionTimedOut;
|
return t.companionRemote.pairing.connectionTimedOut;
|
||||||
} else if (error.contains('Failed to connect')) {
|
} else if (error.contains('Failed to connect')) {
|
||||||
return t.companionRemote.pairing.sessionNotFound;
|
return t.companionRemote.pairing.sessionNotFound;
|
||||||
|
} else if (error.contains('Authentication failed')) {
|
||||||
|
return t.companionRemote.pairing.authFailed;
|
||||||
}
|
}
|
||||||
return t.companionRemote.pairing.failedToConnect(error: error.replaceAll('Exception: ', ''));
|
return t.companionRemote.pairing.failedToConnect(error: error.replaceAll('Exception: ', ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleQrCode(String data) {
|
IconData _platformIcon(String platform) {
|
||||||
// Debounce: don't process the same code twice
|
switch (platform.toLowerCase()) {
|
||||||
if (data == _lastScannedCode) return;
|
case 'macos':
|
||||||
_lastScannedCode = data;
|
return Icons.desktop_mac;
|
||||||
|
case 'windows':
|
||||||
// Strip URL wrapper if present (e.g. "https://plezy.app/scan#ip1,ip2|port|sid|pin")
|
return Icons.desktop_windows;
|
||||||
final payload = data.contains('#') ? data.split('#').last : data;
|
case 'linux':
|
||||||
final parts = payload.split('|');
|
return Icons.computer;
|
||||||
if (parts.length == 4) {
|
default:
|
||||||
final ipsField = parts.first;
|
return Icons.devices;
|
||||||
final port = parts[1];
|
|
||||||
final sessionId = parts[2];
|
|
||||||
final pin = parts[3];
|
|
||||||
|
|
||||||
// Support comma-separated IPs (multi-NIC) or single IP (legacy)
|
|
||||||
final ips = ipsField.split(',');
|
|
||||||
final hostAddresses = ips.map((ip) => '$ip:$port').toList();
|
|
||||||
|
|
||||||
_scannerController?.stop();
|
|
||||||
setState(() {
|
|
||||||
_errorMessage = null;
|
|
||||||
_isConnecting = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
_connectWithCredentialsMulti(sessionId, pin, hostAddresses);
|
|
||||||
} else {
|
|
||||||
setState(() {
|
|
||||||
_errorMessage = t.companionRemote.pairing.invalidQrCode;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _connectWithCredentialsMulti(String sessionId, String pin, List<String> hostAddresses) async {
|
|
||||||
try {
|
|
||||||
final provider = context.read<CompanionRemoteProvider>();
|
|
||||||
await provider.joinSessionMulti(
|
|
||||||
sessionId.trim().toUpperCase(),
|
|
||||||
pin.trim(),
|
|
||||||
hostAddresses.map((a) => a.trim()).toList(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
appLogger.e('Failed to join remote session', error: e);
|
|
||||||
if (!mounted) return;
|
|
||||||
_lastScannedCode = null; // Allow re-scanning
|
|
||||||
setState(() {
|
|
||||||
_isConnecting = false;
|
|
||||||
_errorMessage = _parseErrorMessage(e.toString());
|
|
||||||
});
|
|
||||||
_scannerController?.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _pasteFromClipboard(TextEditingController controller) async {
|
|
||||||
final data = await Clipboard.getData(Clipboard.kTextPlain);
|
|
||||||
if (!mounted) return;
|
|
||||||
if (data?.text != null) {
|
|
||||||
setState(() {
|
|
||||||
controller.text = data!.text!;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,133 +161,18 @@ class _PairingScreenState extends State<PairingScreen> {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(t.companionRemote.connectToDevice),
|
title: Text(t.companionRemote.connectToDevice),
|
||||||
),
|
),
|
||||||
body: Column(
|
body: _buildBody(),
|
||||||
children: [
|
|
||||||
if (_canScan)
|
|
||||||
SegmentedButton<int>(
|
|
||||||
segments: [
|
|
||||||
ButtonSegment(
|
|
||||||
value: _scanTabIndex,
|
|
||||||
label: Text(t.companionRemote.pairing.scan),
|
|
||||||
icon: const Icon(Icons.qr_code_scanner),
|
|
||||||
),
|
|
||||||
ButtonSegment(
|
|
||||||
value: _manualTabIndex,
|
|
||||||
label: Text(t.companionRemote.pairing.manual),
|
|
||||||
icon: const Icon(Icons.keyboard),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
selected: {_selectedTab},
|
|
||||||
onSelectionChanged: (Set<int> selection) {
|
|
||||||
setState(() {
|
|
||||||
_selectedTab = selection.first;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Expanded(child: _buildTabContent()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTabContent() {
|
Widget _buildBody() {
|
||||||
if (_selectedTab == _scanTabIndex && _canScan) return _buildScanTab();
|
|
||||||
return _buildManualEntryTab();
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildScanTab() {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(24.0),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
|
||||||
child: MobileScanner(
|
|
||||||
controller: _scannerController ??= MobileScannerController(),
|
|
||||||
onDetect: (capture) {
|
|
||||||
final barcode = capture.barcodes.firstOrNull;
|
|
||||||
if (barcode?.rawValue != null) {
|
|
||||||
_handleQrCode(barcode!.rawValue!);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
errorBuilder: (context, error) {
|
|
||||||
return Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(24.0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.no_photography, size: 64, color: Colors.grey),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
error.errorCode == MobileScannerErrorCode.permissionDenied
|
|
||||||
? t.companionRemote.pairing.cameraPermissionRequired
|
|
||||||
: t.companionRemote.pairing.cameraError(
|
|
||||||
error: error.errorDetails?.message ?? error.errorCode.name,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
t.companionRemote.pairing.scanInstruction,
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
if (_errorMessage != null) ...[
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Card(
|
|
||||||
color: Theme.of(context).colorScheme.errorContainer,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(12.0),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.error_outline, color: Theme.of(context).colorScheme.onErrorContainer),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
_errorMessage!,
|
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.onErrorContainer),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildManualEntryTab() {
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
padding: const EdgeInsets.all(24.0),
|
padding: const EdgeInsets.all(24.0),
|
||||||
child: Form(
|
|
||||||
key: _formKey,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.keyboard, size: 64, color: Theme.of(context).colorScheme.primary),
|
Icon(Icons.devices, size: 64, color: Theme.of(context).colorScheme.primary),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
t.companionRemote.pairing.pairWithDesktop,
|
t.companionRemote.pairing.pairWithDesktop,
|
||||||
style: Theme.of(context).textTheme.headlineMedium,
|
style: Theme.of(context).textTheme.headlineMedium,
|
||||||
@@ -312,11 +180,12 @@ class _PairingScreenState extends State<PairingScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
t.companionRemote.pairing.enterSessionDetails,
|
t.companionRemote.pairing.discoveryDescription,
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 32),
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
if (_errorMessage != null) ...[
|
if (_errorMessage != null) ...[
|
||||||
Card(
|
Card(
|
||||||
color: Theme.of(context).colorScheme.errorContainer,
|
color: Theme.of(context).colorScheme.errorContainer,
|
||||||
@@ -338,6 +207,139 @@ class _PairingScreenState extends State<PairingScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// Discovered hosts
|
||||||
|
_buildDiscoverySection(),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
const Divider(),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Manual entry fallback
|
||||||
|
_buildManualEntrySection(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDiscoverySection() {
|
||||||
|
if (!_cryptoReady) {
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.warning_amber, size: 48, color: Colors.orange),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
t.companionRemote.pairing.cryptoInitFailed,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_isSearching && _hosts.isEmpty) {
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
child: CircularProgressIndicator(strokeWidth: 3),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
t.companionRemote.pairing.searchingForDevices,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_hosts.isEmpty) {
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.devices_other, size: 48, color: Colors.grey),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
t.companionRemote.pairing.noDevicesFound,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
t.companionRemote.pairing.noDevicesHint,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(color: Colors.grey),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
t.companionRemote.pairing.availableDevices,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
..._hosts.map((host) => Card(
|
||||||
|
child: ListTile(
|
||||||
|
leading: Icon(_platformIcon(host.platform), size: 32),
|
||||||
|
title: Text(host.name),
|
||||||
|
subtitle: Text(host.platform),
|
||||||
|
trailing: _isConnecting
|
||||||
|
? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2))
|
||||||
|
: const Icon(Icons.arrow_forward),
|
||||||
|
onTap: _isConnecting ? null : () => _connectToHost(host),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildManualEntrySection() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
InkWell(
|
||||||
|
onTap: () => setState(() => _showManualEntry = !_showManualEntry),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
_showManualEntry ? Icons.expand_less : Icons.expand_more,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
t.companionRemote.pairing.manualConnection,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_showManualEntry) ...[
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _hostAddressController,
|
controller: _hostAddressController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@@ -345,17 +347,11 @@ class _PairingScreenState extends State<PairingScreen> {
|
|||||||
hintText: t.companionRemote.pairing.hostAddressHint,
|
hintText: t.companionRemote.pairing.hostAddressHint,
|
||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
prefixIcon: const Icon(Icons.computer),
|
prefixIcon: const Icon(Icons.computer),
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: const Icon(Icons.paste),
|
|
||||||
onPressed: () => _pasteFromClipboard(_hostAddressController),
|
|
||||||
tooltip: t.common.paste,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
return t.companionRemote.pairing.validationHostRequired;
|
return t.companionRemote.pairing.validationHostRequired;
|
||||||
}
|
}
|
||||||
// Validate IP:port format
|
|
||||||
final parts = value.split(':');
|
final parts = value.split(':');
|
||||||
if (parts.length != 2) {
|
if (parts.length != 2) {
|
||||||
return t.companionRemote.pairing.validationHostFormat;
|
return t.companionRemote.pairing.validationHostFormat;
|
||||||
@@ -365,103 +361,18 @@ class _PairingScreenState extends State<PairingScreen> {
|
|||||||
enabled: !_isConnecting,
|
enabled: !_isConnecting,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextFormField(
|
|
||||||
controller: _sessionIdController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: t.companionRemote.session.sessionId,
|
|
||||||
hintText: t.companionRemote.pairing.sessionIdHint,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
prefixIcon: const Icon(Icons.vpn_key),
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: const Icon(Icons.paste),
|
|
||||||
onPressed: () => _pasteFromClipboard(_sessionIdController),
|
|
||||||
tooltip: t.common.paste,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
textCapitalization: TextCapitalization.characters,
|
|
||||||
inputFormatters: [
|
|
||||||
FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9]')),
|
|
||||||
LengthLimitingTextInputFormatter(8),
|
|
||||||
TextInputFormatter.withFunction((oldValue, newValue) {
|
|
||||||
return newValue.copyWith(text: newValue.text.toUpperCase());
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return t.companionRemote.pairing.validationSessionIdRequired;
|
|
||||||
}
|
|
||||||
if (value.length != 8) {
|
|
||||||
return t.companionRemote.pairing.validationSessionIdLength;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
enabled: !_isConnecting,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
TextFormField(
|
|
||||||
controller: _pinController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: t.companionRemote.session.pin,
|
|
||||||
hintText: t.companionRemote.pairing.pinHint,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
prefixIcon: const Icon(Icons.lock),
|
|
||||||
suffixIcon: IconButton(
|
|
||||||
icon: const Icon(Icons.paste),
|
|
||||||
onPressed: () => _pasteFromClipboard(_pinController),
|
|
||||||
tooltip: t.common.paste,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(6)],
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return t.companionRemote.pairing.validationPinRequired;
|
|
||||||
}
|
|
||||||
if (value.length != 6) {
|
|
||||||
return t.companionRemote.pairing.validationPinLength;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
enabled: !_isConnecting,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
FilledButton.icon(
|
FilledButton.icon(
|
||||||
onPressed: _isConnecting ? null : _connect,
|
onPressed: _isConnecting ? null : _connectManual,
|
||||||
icon: _isConnecting
|
icon: _isConnecting
|
||||||
? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))
|
? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))
|
||||||
: const Icon(Icons.link),
|
: const Icon(Icons.link),
|
||||||
label: Text(_isConnecting ? t.companionRemote.pairing.connecting : t.common.connect),
|
label: Text(_isConnecting ? t.companionRemote.pairing.connecting : t.common.connect),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 32),
|
|
||||||
const Divider(),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(t.companionRemote.pairing.tips, style: Theme.of(context).textTheme.titleMedium),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_buildTipCard(context, Icons.computer, t.companionRemote.pairing.tipDesktop),
|
|
||||||
if (_canScan) ...[
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_buildTipCard(context, Icons.qr_code, t.companionRemote.pairing.tipScan),
|
|
||||||
],
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
_buildTipCard(context, Icons.wifi, t.companionRemote.pairing.tipWifi),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildTipCard(BuildContext context, IconData icon, String text) {
|
|
||||||
return Card(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(12.0),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(icon, size: 20, color: Theme.of(context).colorScheme.primary),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Expanded(child: Text(text, style: Theme.of(context).textTheme.bodySmall)),
|
|
||||||
],
|
],
|
||||||
),
|
],
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -436,6 +436,9 @@ class _MainScreenState extends State<MainScreen> with RouteAware, WindowListener
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Auto-start host server if setting enabled
|
||||||
|
_autoStartCompanionRemoteServer(companionRemote);
|
||||||
|
|
||||||
final receiver = CompanionRemoteReceiver.instance;
|
final receiver = CompanionRemoteReceiver.instance;
|
||||||
|
|
||||||
receiver.onTabNext = () {
|
receiver.onTabNext = () {
|
||||||
@@ -466,6 +469,21 @@ class _MainScreenState extends State<MainScreen> with RouteAware, WindowListener
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _autoStartCompanionRemoteServer(CompanionRemoteProvider companionRemote) async {
|
||||||
|
try {
|
||||||
|
final settings = await SettingsService.getInstance();
|
||||||
|
if (!settings.getEnableCompanionRemoteServer()) return;
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
final home = context.read<UserProfileProvider>().home;
|
||||||
|
if (await companionRemote.ensureCryptoReady(home)) {
|
||||||
|
await companionRemote.startHostServer();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
appLogger.e('CompanionRemote: Failed to auto-start server', error: e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
|||||||
String _creditsPattern = settings.SettingsService.defaultCreditsPattern;
|
String _creditsPattern = settings.SettingsService.defaultCreditsPattern;
|
||||||
int _maxVolume = 100;
|
int _maxVolume = 100;
|
||||||
bool _enableDiscordRPC = false;
|
bool _enableDiscordRPC = false;
|
||||||
|
bool _enableCompanionRemoteServer = false;
|
||||||
bool _autoPip = true;
|
bool _autoPip = true;
|
||||||
bool _matchContentFrameRate = false;
|
bool _matchContentFrameRate = false;
|
||||||
bool _matchRefreshRate = false;
|
bool _matchRefreshRate = false;
|
||||||
@@ -84,6 +85,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
|||||||
_creditsPattern = _settingsService.getCreditsPattern();
|
_creditsPattern = _settingsService.getCreditsPattern();
|
||||||
_maxVolume = _settingsService.getMaxVolume();
|
_maxVolume = _settingsService.getMaxVolume();
|
||||||
_enableDiscordRPC = _settingsService.getEnableDiscordRPC();
|
_enableDiscordRPC = _settingsService.getEnableDiscordRPC();
|
||||||
|
_enableCompanionRemoteServer = _settingsService.getEnableCompanionRemoteServer();
|
||||||
_autoPip = _settingsService.getAutoPip();
|
_autoPip = _settingsService.getAutoPip();
|
||||||
_matchContentFrameRate = _settingsService.getMatchContentFrameRate();
|
_matchContentFrameRate = _settingsService.getMatchContentFrameRate();
|
||||||
_matchRefreshRate = _settingsService.getMatchRefreshRate();
|
_matchRefreshRate = _settingsService.getMatchRefreshRate();
|
||||||
@@ -142,6 +144,7 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
|||||||
// --- Behavior ---
|
// --- Behavior ---
|
||||||
SettingsSectionHeader(t.settings.behavior),
|
SettingsSectionHeader(t.settings.behavior),
|
||||||
if (DiscordRPCService.isAvailable) _buildDiscordRPC(),
|
if (DiscordRPCService.isAvailable) _buildDiscordRPC(),
|
||||||
|
if (PlatformDetector.shouldActAsRemoteHost(context)) _buildCompanionRemoteServer(),
|
||||||
_buildRememberTrackSelections(),
|
_buildRememberTrackSelections(),
|
||||||
if (!isMobile) _buildClickVideoTogglesPlayback(),
|
if (!isMobile) _buildClickVideoTogglesPlayback(),
|
||||||
|
|
||||||
@@ -494,6 +497,19 @@ class _PlaybackSettingsScreenState extends State<PlaybackSettingsScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildCompanionRemoteServer() {
|
||||||
|
return SwitchListTile(
|
||||||
|
secondary: const AppIcon(Symbols.phone_android_rounded, fill: 1),
|
||||||
|
title: Text(t.settings.companionRemoteServer),
|
||||||
|
subtitle: Text(t.settings.companionRemoteServerDescription),
|
||||||
|
value: _enableCompanionRemoteServer,
|
||||||
|
onChanged: (value) async {
|
||||||
|
setState(() => _enableCompanionRemoteServer = value);
|
||||||
|
await _settingsService.setEnableCompanionRemoteServer(value);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildRememberTrackSelections() {
|
Widget _buildRememberTrackSelections() {
|
||||||
return SwitchListTile(
|
return SwitchListTile(
|
||||||
secondary: const AppIcon(Symbols.bookmark_rounded, fill: 1),
|
secondary: const AppIcon(Symbols.bookmark_rounded, fill: 1),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import '../../models/companion_remote/remote_command.dart';
|
|||||||
import '../../models/companion_remote/remote_session.dart';
|
import '../../models/companion_remote/remote_session.dart';
|
||||||
import '../../utils/app_logger.dart';
|
import '../../utils/app_logger.dart';
|
||||||
import '../base_peer_service.dart';
|
import '../base_peer_service.dart';
|
||||||
|
import 'remote_auth_service.dart';
|
||||||
|
|
||||||
// Re-export so callers that import from here get the types.
|
// Re-export so callers that import from here get the types.
|
||||||
export '../base_peer_service.dart' show PeerError, PeerErrorType;
|
export '../base_peer_service.dart' show PeerError, PeerErrorType;
|
||||||
@@ -27,12 +28,16 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
// Client-side (remote) fields
|
// Client-side (remote) fields
|
||||||
IOWebSocketChannel? _channel;
|
IOWebSocketChannel? _channel;
|
||||||
|
|
||||||
String? _sessionId;
|
|
||||||
String? _pin;
|
|
||||||
String? _myPeerId;
|
String? _myPeerId;
|
||||||
String? _hostAddress; // Format: "ip:port"
|
String? _hostAddress; // Format: "ip:port"
|
||||||
RemoteSessionRole? _role;
|
RemoteSessionRole? _role;
|
||||||
|
|
||||||
|
// Encrypted channel state
|
||||||
|
List<int>? _sessionEncKey;
|
||||||
|
int _sendCounter = 0;
|
||||||
|
int _recvCounter = 0;
|
||||||
|
bool _isAuthenticated = false;
|
||||||
|
|
||||||
final _commandReceivedController = StreamController<RemoteCommand>.broadcast();
|
final _commandReceivedController = StreamController<RemoteCommand>.broadcast();
|
||||||
final _deviceConnectedController = StreamController<RemoteDevice>.broadcast();
|
final _deviceConnectedController = StreamController<RemoteDevice>.broadcast();
|
||||||
final _deviceDisconnectedController = StreamController<void>.broadcast();
|
final _deviceDisconnectedController = StreamController<void>.broadcast();
|
||||||
@@ -45,9 +50,9 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
@override
|
@override
|
||||||
Duration get pongTimeout => Duration.zero; // No pong timeout; host just replies inline
|
Duration get pongTimeout => Duration.zero; // No pong timeout; host just replies inline
|
||||||
|
|
||||||
// Auth rate limiting
|
// Auth rate limiting (per source IP)
|
||||||
int _failedAuthAttempts = 0;
|
final Map<String, int> _failedAuthAttempts = {};
|
||||||
DateTime? _authLockoutUntil;
|
final Map<String, DateTime> _authLockouts = {};
|
||||||
static const int _maxFailedAuthAttempts = 5;
|
static const int _maxFailedAuthAttempts = 5;
|
||||||
static const Duration _authLockoutDuration = Duration(seconds: 30);
|
static const Duration _authLockoutDuration = Duration(seconds: 30);
|
||||||
|
|
||||||
@@ -57,25 +62,12 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
Stream<RemotePeerError> get onError => _errorController.stream;
|
Stream<RemotePeerError> get onError => _errorController.stream;
|
||||||
Stream<RemoteSessionStatus> get onConnectionStateChanged => _connectionStateController.stream;
|
Stream<RemoteSessionStatus> get onConnectionStateChanged => _connectionStateController.stream;
|
||||||
|
|
||||||
String? get sessionId => _sessionId;
|
|
||||||
String? get pin => _pin;
|
|
||||||
String? get myPeerId => _myPeerId;
|
String? get myPeerId => _myPeerId;
|
||||||
String? get hostAddress => _hostAddress;
|
String? get hostAddress => _hostAddress;
|
||||||
RemoteSessionRole? get role => _role;
|
RemoteSessionRole? get role => _role;
|
||||||
bool get isHost => _role == RemoteSessionRole.host;
|
bool get isHost => _role == RemoteSessionRole.host;
|
||||||
bool get isConnected => _clientSocket != null || (_channel != null && _channel?.closeCode == null);
|
bool get isConnected => _clientSocket != null || (_channel != null && _channel?.closeCode == null);
|
||||||
|
|
||||||
String _generateSessionId() {
|
|
||||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
||||||
final random = Random.secure();
|
|
||||||
return List.generate(8, (index) => chars[random.nextInt(chars.length)]).join();
|
|
||||||
}
|
|
||||||
|
|
||||||
String _generatePin() {
|
|
||||||
final random = Random.secure();
|
|
||||||
return List.generate(6, (index) => random.nextInt(10).toString()).join();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<String>> _getAllLocalIpAddresses() async {
|
Future<List<String>> _getAllLocalIpAddresses() async {
|
||||||
try {
|
try {
|
||||||
final interfaces = await NetworkInterface.list(type: InternetAddressType.IPv4);
|
final interfaces = await NetworkInterface.list(type: InternetAddressType.IPv4);
|
||||||
@@ -109,21 +101,22 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<({String sessionId, String pin, List<String> addresses})> createSession(
|
/// Create a host session — starts WebSocket server, returns local addresses and port.
|
||||||
|
Future<({List<String> addresses, int port})> createSession(
|
||||||
String deviceName,
|
String deviceName,
|
||||||
String platform,
|
String platform,
|
||||||
|
List<int> homeSecret,
|
||||||
|
String clientIdentifier,
|
||||||
|
List<String> homeUserUUIDs,
|
||||||
) async {
|
) async {
|
||||||
if (_server != null) {
|
if (_server != null) {
|
||||||
await disconnect();
|
await disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
_role = RemoteSessionRole.host;
|
_role = RemoteSessionRole.host;
|
||||||
_sessionId = _generateSessionId();
|
_myPeerId = 'host';
|
||||||
_pin = _generatePin();
|
|
||||||
_myPeerId = 'host-$_sessionId';
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Try preferred port first, fallback to OS-assigned port
|
|
||||||
const int preferredPort = 48632;
|
const int preferredPort = 48632;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -141,12 +134,20 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
|
|
||||||
appLogger.d('CompanionRemote: Host server started, addresses: $addresses');
|
appLogger.d('CompanionRemote: Host server started, addresses: $addresses');
|
||||||
|
|
||||||
// Listen for WebSocket connections
|
|
||||||
_server!.listen((HttpRequest request) async {
|
_server!.listen((HttpRequest request) async {
|
||||||
if (request.uri.path == '/ws') {
|
if (request.uri.path == '/ws') {
|
||||||
try {
|
try {
|
||||||
final socket = await WebSocketTransformer.upgrade(request);
|
final socket = await WebSocketTransformer.upgrade(request);
|
||||||
_handleNewWebSocketConnection(socket, deviceName, platform);
|
final sourceIp = request.connectionInfo?.remoteAddress.address ?? 'unknown';
|
||||||
|
_handleNewWebSocketConnection(
|
||||||
|
socket,
|
||||||
|
deviceName,
|
||||||
|
platform,
|
||||||
|
homeSecret,
|
||||||
|
clientIdentifier,
|
||||||
|
homeUserUUIDs,
|
||||||
|
sourceIp,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
appLogger.e('CompanionRemote: Failed to upgrade WebSocket', error: e);
|
appLogger.e('CompanionRemote: Failed to upgrade WebSocket', error: e);
|
||||||
}
|
}
|
||||||
@@ -158,7 +159,7 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
|
|
||||||
_connectionStateController.add(RemoteSessionStatus.connected);
|
_connectionStateController.add(RemoteSessionStatus.connected);
|
||||||
|
|
||||||
return (sessionId: _sessionId!, pin: _pin!, addresses: addresses);
|
return (addresses: addresses, port: port);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
appLogger.e('CompanionRemote: Failed to create server', error: e);
|
appLogger.e('CompanionRemote: Failed to create server', error: e);
|
||||||
_errorController.add(
|
_errorController.add(
|
||||||
@@ -172,11 +173,36 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleNewWebSocketConnection(WebSocket socket, String hostDeviceName, String hostPlatform) {
|
void _handleNewWebSocketConnection(
|
||||||
appLogger.d('CompanionRemote: New WebSocket connection');
|
WebSocket socket,
|
||||||
|
String hostDeviceName,
|
||||||
|
String hostPlatform,
|
||||||
|
List<int> homeSecret,
|
||||||
|
String hostClientId,
|
||||||
|
List<String> homeUserUUIDs,
|
||||||
|
String sourceIp,
|
||||||
|
) {
|
||||||
|
appLogger.d('CompanionRemote: New WebSocket connection from $sourceIp');
|
||||||
|
|
||||||
bool isAuthenticated = false;
|
bool isAuthenticated = false;
|
||||||
Timer? authTimeout;
|
Timer? authTimeout;
|
||||||
|
final auth = RemoteAuthService.instance;
|
||||||
|
final hostNonce = auth.generateNonce();
|
||||||
|
|
||||||
|
// Check rate limiting
|
||||||
|
final lockout = _authLockouts[sourceIp];
|
||||||
|
if (lockout != null && DateTime.now().isBefore(lockout)) {
|
||||||
|
appLogger.w('CompanionRemote: Connection from $sourceIp rejected (rate limited)');
|
||||||
|
socket.close(4005, 'Rate limited');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send challenge: hostNonce + hostClientId
|
||||||
|
socket.add(jsonEncode({
|
||||||
|
'type': 'challenge',
|
||||||
|
'nonce': base64Encode(hostNonce),
|
||||||
|
'hostClientId': hostClientId,
|
||||||
|
}));
|
||||||
|
|
||||||
// Authentication timeout
|
// Authentication timeout
|
||||||
authTimeout = Timer(const Duration(seconds: 10), () {
|
authTimeout = Timer(const Duration(seconds: 10), () {
|
||||||
@@ -187,31 +213,69 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.listen(
|
socket.listen(
|
||||||
(data) {
|
(data) async {
|
||||||
try {
|
try {
|
||||||
|
if (!isAuthenticated) {
|
||||||
final json = jsonDecode(data as String) as Map<String, dynamic>;
|
final json = jsonDecode(data as String) as Map<String, dynamic>;
|
||||||
|
|
||||||
if (!isAuthenticated) {
|
|
||||||
// First message must be authentication
|
|
||||||
if (json['type'] == 'auth') {
|
if (json['type'] == 'auth') {
|
||||||
// Rate limiting: reject if locked out
|
final authTag = json['authTag'] as String?;
|
||||||
if (_authLockoutUntil != null && DateTime.now().isBefore(_authLockoutUntil!)) {
|
final clientNonceB64 = json['clientNonce'] as String?;
|
||||||
appLogger.w('CompanionRemote: Auth attempt rejected (rate limited)');
|
final userUUID = json['userUUID'] as String?;
|
||||||
socket.add(jsonEncode({'type': 'authFailed', 'message': 'Too many attempts. Try again later.'}));
|
final clientIdentifier = json['clientIdentifier'] as String?;
|
||||||
socket.close(4005, 'Rate limited');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final sessionId = json['sessionId'] as String?;
|
|
||||||
final pin = json['pin'] as String?;
|
|
||||||
final deviceName = json['deviceName'] as String?;
|
final deviceName = json['deviceName'] as String?;
|
||||||
final platform = json['platform'] as String?;
|
final platform = json['platform'] as String?;
|
||||||
|
|
||||||
if (sessionId == _sessionId && pin == _pin) {
|
if (authTag == null ||
|
||||||
_failedAuthAttempts = 0;
|
clientNonceB64 == null ||
|
||||||
|
userUUID == null ||
|
||||||
|
clientIdentifier == null ||
|
||||||
|
deviceName == null ||
|
||||||
|
platform == null) {
|
||||||
|
socket.add(jsonEncode({'type': 'authFailed'}));
|
||||||
|
socket.close(4003, 'Authentication failed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final clientNonce = base64Decode(clientNonceB64);
|
||||||
|
|
||||||
|
// Verify userUUID is in home users list
|
||||||
|
if (!homeUserUUIDs.contains(userUUID)) {
|
||||||
|
_recordFailedAuth(sourceIp);
|
||||||
|
appLogger.w('CompanionRemote: Auth failed — unknown user');
|
||||||
|
socket.add(jsonEncode({'type': 'authFailed'}));
|
||||||
|
socket.close(4003, 'Authentication failed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify auth tag
|
||||||
|
final valid = auth.verifyAuthTag(
|
||||||
|
authTag: authTag,
|
||||||
|
homeSecret: homeSecret,
|
||||||
|
hostNonce: hostNonce,
|
||||||
|
clientNonce: clientNonce,
|
||||||
|
hostClientId: hostClientId,
|
||||||
|
userUUID: userUUID,
|
||||||
|
clientIdentifier: clientIdentifier,
|
||||||
|
deviceName: deviceName,
|
||||||
|
platform: platform,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!valid) {
|
||||||
|
_recordFailedAuth(sourceIp);
|
||||||
|
appLogger.w('CompanionRemote: Auth failed — invalid auth tag');
|
||||||
|
socket.add(jsonEncode({'type': 'authFailed'}));
|
||||||
|
socket.close(4003, 'Authentication failed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth success — derive per-session encryption key
|
||||||
|
_failedAuthAttempts.remove(sourceIp);
|
||||||
isAuthenticated = true;
|
isAuthenticated = true;
|
||||||
authTimeout?.cancel();
|
authTimeout?.cancel();
|
||||||
|
|
||||||
|
final sessionEncKey = await auth.deriveSessionEncKey(homeSecret, hostNonce, clientNonce);
|
||||||
|
|
||||||
// Close existing client if present
|
// Close existing client if present
|
||||||
if (_clientSocket != null) {
|
if (_clientSocket != null) {
|
||||||
appLogger.d('CompanionRemote: Replacing existing client connection');
|
appLogger.d('CompanionRemote: Replacing existing client connection');
|
||||||
@@ -219,45 +283,37 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_clientSocket = socket;
|
_clientSocket = socket;
|
||||||
|
_sessionEncKey = sessionEncKey;
|
||||||
|
_sendCounter = 0;
|
||||||
|
_recvCounter = 0;
|
||||||
|
_isAuthenticated = true;
|
||||||
|
|
||||||
appLogger.d('CompanionRemote: Client authenticated: $deviceName ($platform)');
|
appLogger.d('CompanionRemote: Client authenticated: $deviceName ($platform)');
|
||||||
|
|
||||||
// Send auth success
|
// Send encrypted authSuccess
|
||||||
socket.add(jsonEncode({'type': 'authSuccess'}));
|
await _sendEncryptedToSocket(socket, jsonEncode({'type': 'authSuccess'}));
|
||||||
|
|
||||||
// Notify connection
|
// Notify connection
|
||||||
final device = RemoteDevice(
|
final device = RemoteDevice(
|
||||||
id: 'remote-client',
|
id: 'remote-client',
|
||||||
name: deviceName ?? 'Unknown Device',
|
name: deviceName,
|
||||||
platform: platform ?? 'unknown',
|
platform: platform,
|
||||||
);
|
);
|
||||||
_deviceConnectedController.add(device);
|
_deviceConnectedController.add(device);
|
||||||
_connectionStateController.add(RemoteSessionStatus.connected);
|
_connectionStateController.add(RemoteSessionStatus.connected);
|
||||||
|
|
||||||
// Send device info
|
// Send device info
|
||||||
sendDeviceInfo(hostDeviceName, hostPlatform);
|
sendDeviceInfo(hostDeviceName, hostPlatform);
|
||||||
|
|
||||||
// Note: Client sends keepalive pings, host only responds with pongs
|
|
||||||
} else {
|
|
||||||
_failedAuthAttempts++;
|
|
||||||
if (_failedAuthAttempts >= _maxFailedAuthAttempts) {
|
|
||||||
_authLockoutUntil = DateTime.now().add(_authLockoutDuration);
|
|
||||||
appLogger.w(
|
|
||||||
'CompanionRemote: Too many failed auth attempts, locked out for ${_authLockoutDuration.inSeconds}s',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
appLogger.w(
|
|
||||||
'CompanionRemote: Invalid credentials (attempt $_failedAuthAttempts/$_maxFailedAuthAttempts)',
|
|
||||||
);
|
|
||||||
socket.add(jsonEncode({'type': 'authFailed', 'message': 'Invalid session ID or PIN'}));
|
|
||||||
socket.close(4003, 'Invalid credentials');
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
appLogger.w('CompanionRemote: Expected auth, got ${json['type']}');
|
appLogger.w('CompanionRemote: Expected auth, got ${json['type']}');
|
||||||
socket.close(4002, 'Authentication required');
|
socket.close(4002, 'Authentication required');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Handle regular commands
|
// Encrypted command — data is binary
|
||||||
|
final decrypted = await _decryptIncoming(data);
|
||||||
|
if (decrypted == null) return;
|
||||||
|
|
||||||
|
final json = jsonDecode(decrypted) as Map<String, dynamic>;
|
||||||
final command = RemoteCommand.fromJson(json);
|
final command = RemoteCommand.fromJson(json);
|
||||||
appLogger.d('CompanionRemote: Received command: ${command.type}');
|
appLogger.d('CompanionRemote: Received command: ${command.type}');
|
||||||
|
|
||||||
@@ -280,6 +336,8 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
appLogger.d('CompanionRemote: WebSocket connection closed');
|
appLogger.d('CompanionRemote: WebSocket connection closed');
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
_clientSocket = null;
|
_clientSocket = null;
|
||||||
|
_sessionEncKey = null;
|
||||||
|
_isAuthenticated = false;
|
||||||
_deviceDisconnectedController.add(null);
|
_deviceDisconnectedController.add(null);
|
||||||
_connectionStateController.add(RemoteSessionStatus.disconnected);
|
_connectionStateController.add(RemoteSessionStatus.disconnected);
|
||||||
stopKeepalive();
|
stopKeepalive();
|
||||||
@@ -299,18 +357,35 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> joinSession(String sessionId, String pin, String deviceName, String platform, String hostAddress) async {
|
void _recordFailedAuth(String sourceIp) {
|
||||||
|
final attempts = (_failedAuthAttempts[sourceIp] ?? 0) + 1;
|
||||||
|
_failedAuthAttempts[sourceIp] = attempts;
|
||||||
|
if (attempts >= _maxFailedAuthAttempts) {
|
||||||
|
_authLockouts[sourceIp] = DateTime.now().add(_authLockoutDuration);
|
||||||
|
appLogger.w('CompanionRemote: IP $sourceIp locked out for ${_authLockoutDuration.inSeconds}s');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Join a host session as a remote client.
|
||||||
|
Future<void> joinSession(
|
||||||
|
String deviceName,
|
||||||
|
String platform,
|
||||||
|
String hostAddress,
|
||||||
|
List<int> homeSecret,
|
||||||
|
String hostClientId,
|
||||||
|
String userUUID,
|
||||||
|
String clientIdentifier,
|
||||||
|
) async {
|
||||||
if (_channel != null) {
|
if (_channel != null) {
|
||||||
await disconnect();
|
await disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
_role = RemoteSessionRole.remote;
|
_role = RemoteSessionRole.remote;
|
||||||
_sessionId = sessionId.toUpperCase();
|
|
||||||
_pin = pin;
|
|
||||||
_hostAddress = hostAddress;
|
_hostAddress = hostAddress;
|
||||||
_myPeerId = 'remote-${Random.secure().nextInt(99999)}';
|
_myPeerId = 'remote-${Random.secure().nextInt(99999)}';
|
||||||
|
|
||||||
final completer = Completer<void>();
|
final completer = Completer<void>();
|
||||||
|
final auth = RemoteAuthService.instance;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final url = 'ws://$hostAddress/ws';
|
final url = 'ws://$hostAddress/ws';
|
||||||
@@ -321,51 +396,19 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
_channel = IOWebSocketChannel.connect(Uri.parse(url));
|
_channel = IOWebSocketChannel.connect(Uri.parse(url));
|
||||||
await _channel!.ready;
|
await _channel!.ready;
|
||||||
|
|
||||||
// Send authentication message
|
List<int>? hostNonce;
|
||||||
final authMessage = jsonEncode({
|
List<int>? clientNonce;
|
||||||
'type': 'auth',
|
String? receivedHostClientId;
|
||||||
'sessionId': _sessionId,
|
|
||||||
'pin': _pin,
|
|
||||||
'deviceName': deviceName,
|
|
||||||
'platform': platform,
|
|
||||||
});
|
|
||||||
_channel!.sink.add(authMessage);
|
|
||||||
|
|
||||||
// Listen for messages
|
|
||||||
_channel!.stream.listen(
|
_channel!.stream.listen(
|
||||||
(data) {
|
(data) async {
|
||||||
try {
|
try {
|
||||||
final json = jsonDecode(data as String) as Map<String, dynamic>;
|
if (_isAuthenticated) {
|
||||||
final messageType = json['type'] as String?;
|
// Post-auth: all messages are encrypted binary
|
||||||
|
final decrypted = await _decryptIncoming(data);
|
||||||
|
if (decrypted == null) return;
|
||||||
|
|
||||||
if (messageType == 'authSuccess') {
|
final json = jsonDecode(decrypted) as Map<String, dynamic>;
|
||||||
appLogger.d('CompanionRemote: Authentication successful');
|
|
||||||
|
|
||||||
if (!completer.isCompleted) {
|
|
||||||
completer.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
final device = RemoteDevice(id: 'host', name: 'Desktop', platform: 'desktop');
|
|
||||||
_deviceConnectedController.add(device);
|
|
||||||
_connectionStateController.add(RemoteSessionStatus.connected);
|
|
||||||
|
|
||||||
// Send device info
|
|
||||||
sendDeviceInfo(deviceName, platform);
|
|
||||||
|
|
||||||
// Start ping timer
|
|
||||||
startKeepalive();
|
|
||||||
} else if (messageType == 'authFailed') {
|
|
||||||
final message = json['message'] as String? ?? 'Authentication failed';
|
|
||||||
appLogger.w('CompanionRemote: $message');
|
|
||||||
|
|
||||||
if (!completer.isCompleted) {
|
|
||||||
completer.completeError(RemotePeerError(type: RemotePeerErrorType.authFailed, message: message));
|
|
||||||
}
|
|
||||||
|
|
||||||
_errorController.add(RemotePeerError(type: RemotePeerErrorType.authFailed, message: message));
|
|
||||||
_connectionStateController.add(RemoteSessionStatus.error);
|
|
||||||
} else {
|
|
||||||
// Regular command
|
|
||||||
final command = RemoteCommand.fromJson(json);
|
final command = RemoteCommand.fromJson(json);
|
||||||
appLogger.d('CompanionRemote: Received command: ${command.type}');
|
appLogger.d('CompanionRemote: Received command: ${command.type}');
|
||||||
|
|
||||||
@@ -378,6 +421,89 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
if (command.type == RemoteCommandType.ping) {
|
if (command.type == RemoteCommandType.ping) {
|
||||||
_sendPong();
|
_sendPong();
|
||||||
}
|
}
|
||||||
|
} else if (_sessionEncKey != null) {
|
||||||
|
// Keys derived, waiting for encrypted authSuccess
|
||||||
|
final decrypted = await _decryptIncoming(data);
|
||||||
|
if (decrypted == null) return;
|
||||||
|
|
||||||
|
final json = jsonDecode(decrypted) as Map<String, dynamic>;
|
||||||
|
if (json['type'] == 'authSuccess') {
|
||||||
|
_isAuthenticated = true;
|
||||||
|
appLogger.d('CompanionRemote: Authentication successful');
|
||||||
|
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
final device = RemoteDevice(id: 'host', name: 'Desktop', platform: 'desktop');
|
||||||
|
_deviceConnectedController.add(device);
|
||||||
|
_connectionStateController.add(RemoteSessionStatus.connected);
|
||||||
|
|
||||||
|
sendDeviceInfo(deviceName, platform);
|
||||||
|
startKeepalive();
|
||||||
|
} else if (json['type'] == 'authFailed') {
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.completeError(
|
||||||
|
const RemotePeerError(type: RemotePeerErrorType.authFailed, message: 'Authentication failed'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Pre-auth: plaintext handshake
|
||||||
|
final json = jsonDecode(data as String) as Map<String, dynamic>;
|
||||||
|
final messageType = json['type'] as String?;
|
||||||
|
|
||||||
|
if (messageType == 'challenge') {
|
||||||
|
hostNonce = base64Decode(json['nonce'] as String);
|
||||||
|
receivedHostClientId = json['hostClientId'] as String;
|
||||||
|
clientNonce = auth.generateNonce();
|
||||||
|
|
||||||
|
if (hostClientId.isNotEmpty && receivedHostClientId != hostClientId) {
|
||||||
|
appLogger.w('CompanionRemote: Host client ID mismatch');
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.completeError(
|
||||||
|
const RemotePeerError(type: RemotePeerErrorType.authFailed, message: 'Host identity mismatch'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final authTag = auth.computeAuthTag(
|
||||||
|
homeSecret: homeSecret,
|
||||||
|
hostNonce: hostNonce!,
|
||||||
|
clientNonce: clientNonce!,
|
||||||
|
hostClientId: receivedHostClientId!,
|
||||||
|
userUUID: userUUID,
|
||||||
|
clientIdentifier: clientIdentifier,
|
||||||
|
deviceName: deviceName,
|
||||||
|
platform: platform,
|
||||||
|
);
|
||||||
|
|
||||||
|
_channel!.sink.add(jsonEncode({
|
||||||
|
'type': 'auth',
|
||||||
|
'clientNonce': base64Encode(clientNonce!),
|
||||||
|
'userUUID': userUUID,
|
||||||
|
'clientIdentifier': clientIdentifier,
|
||||||
|
'deviceName': deviceName,
|
||||||
|
'platform': platform,
|
||||||
|
'authTag': authTag,
|
||||||
|
}));
|
||||||
|
|
||||||
|
_sessionEncKey = await auth.deriveSessionEncKey(homeSecret, hostNonce!, clientNonce!);
|
||||||
|
_sendCounter = 0;
|
||||||
|
_recvCounter = 0;
|
||||||
|
} else if (messageType == 'authFailed') {
|
||||||
|
appLogger.w('CompanionRemote: Authentication failed');
|
||||||
|
if (!completer.isCompleted) {
|
||||||
|
completer.completeError(
|
||||||
|
const RemotePeerError(type: RemotePeerErrorType.authFailed, message: 'Authentication failed'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_errorController.add(
|
||||||
|
const RemotePeerError(type: RemotePeerErrorType.authFailed, message: 'Authentication failed'),
|
||||||
|
);
|
||||||
|
_connectionStateController.add(RemoteSessionStatus.error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
appLogger.e('CompanionRemote: Failed to parse message', error: e);
|
appLogger.e('CompanionRemote: Failed to parse message', error: e);
|
||||||
@@ -387,8 +513,9 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
appLogger.d('CompanionRemote: Connection closed');
|
appLogger.d('CompanionRemote: Connection closed');
|
||||||
_deviceDisconnectedController.add(null);
|
_deviceDisconnectedController.add(null);
|
||||||
_connectionStateController.add(RemoteSessionStatus.disconnected);
|
_connectionStateController.add(RemoteSessionStatus.disconnected);
|
||||||
|
_isAuthenticated = false;
|
||||||
|
_sessionEncKey = null;
|
||||||
stopKeepalive();
|
stopKeepalive();
|
||||||
// Reconnection is handled by CompanionRemoteProvider
|
|
||||||
},
|
},
|
||||||
onError: (error) {
|
onError: (error) {
|
||||||
appLogger.e('CompanionRemote: Connection error', error: error);
|
appLogger.e('CompanionRemote: Connection error', error: error);
|
||||||
@@ -422,7 +549,6 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
return completer.future.timeout(
|
return completer.future.timeout(
|
||||||
const Duration(seconds: 15),
|
const Duration(seconds: 15),
|
||||||
onTimeout: () async {
|
onTimeout: () async {
|
||||||
// Clean up channel on timeout
|
|
||||||
if (_channel != null) {
|
if (_channel != null) {
|
||||||
try {
|
try {
|
||||||
await _channel!.sink.close();
|
await _channel!.sink.close();
|
||||||
@@ -435,21 +561,23 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Race WebSocket connections to multiple host addresses in parallel.
|
/// Race WebSocket connections to multiple host addresses in parallel.
|
||||||
/// Returns the winning address and sets up a proper managed connection via [joinSession].
|
|
||||||
Future<String> joinSessionRacing(
|
Future<String> joinSessionRacing(
|
||||||
String sessionId,
|
|
||||||
String pin,
|
|
||||||
String deviceName,
|
String deviceName,
|
||||||
String platform,
|
String platform,
|
||||||
List<String> hostAddresses,
|
List<String> hostAddresses,
|
||||||
|
List<int> homeSecret,
|
||||||
|
String hostClientId,
|
||||||
|
String userUUID,
|
||||||
|
String clientIdentifier,
|
||||||
) async {
|
) async {
|
||||||
if (hostAddresses.length == 1) {
|
if (hostAddresses.length == 1) {
|
||||||
await joinSession(sessionId, pin, deviceName, platform, hostAddresses.first);
|
await joinSession(deviceName, platform, hostAddresses.first, homeSecret, hostClientId, userUUID, clientIdentifier);
|
||||||
return hostAddresses.first;
|
return hostAddresses.first;
|
||||||
}
|
}
|
||||||
|
|
||||||
appLogger.d('CompanionRemote: Racing connections to ${hostAddresses.length} addresses');
|
appLogger.d('CompanionRemote: Racing connections to ${hostAddresses.length} addresses');
|
||||||
|
|
||||||
|
// Race: try to connect to all addresses, first one to get a challenge wins
|
||||||
final completer = Completer<String>();
|
final completer = Completer<String>();
|
||||||
final channels = <IOWebSocketChannel>[];
|
final channels = <IOWebSocketChannel>[];
|
||||||
final subs = <StreamSubscription>[];
|
final subs = <StreamSubscription>[];
|
||||||
@@ -471,20 +599,12 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
final channel = IOWebSocketChannel.connect(Uri.parse(url), connectTimeout: const Duration(seconds: 5));
|
final channel = IOWebSocketChannel.connect(Uri.parse(url), connectTimeout: const Duration(seconds: 5));
|
||||||
channels.add(channel);
|
channels.add(channel);
|
||||||
|
|
||||||
// Send auth immediately
|
|
||||||
channel.sink.add(jsonEncode({
|
|
||||||
'type': 'auth',
|
|
||||||
'sessionId': sessionId.toUpperCase(),
|
|
||||||
'pin': pin,
|
|
||||||
'deviceName': deviceName,
|
|
||||||
'platform': platform,
|
|
||||||
}));
|
|
||||||
|
|
||||||
final sub = channel.stream.listen(
|
final sub = channel.stream.listen(
|
||||||
(data) {
|
(data) {
|
||||||
try {
|
try {
|
||||||
final json = jsonDecode(data as String) as Map<String, dynamic>;
|
final json = jsonDecode(data as String) as Map<String, dynamic>;
|
||||||
if (json['type'] == 'authSuccess' && !completer.isCompleted) {
|
// First address to send us a challenge wins the race
|
||||||
|
if (json['type'] == 'challenge' && !completer.isCompleted) {
|
||||||
appLogger.d('CompanionRemote: Race winner: $address');
|
appLogger.d('CompanionRemote: Race winner: $address');
|
||||||
completer.complete(address);
|
completer.complete(address);
|
||||||
}
|
}
|
||||||
@@ -507,11 +627,12 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final winner = await completer.future.namedTimeout(const Duration(seconds: 10), operation: 'CompanionRemote race connect');
|
final winner =
|
||||||
|
await completer.future.namedTimeout(const Duration(seconds: 10), operation: 'CompanionRemote race connect');
|
||||||
cleanup();
|
cleanup();
|
||||||
|
|
||||||
// Now set up the proper managed connection on the winning address
|
// Set up the proper managed connection on the winning address
|
||||||
await joinSession(sessionId, pin, deviceName, platform, winner);
|
await joinSession(deviceName, platform, winner, homeSecret, hostClientId, userUUID, clientIdentifier);
|
||||||
return winner;
|
return winner;
|
||||||
} on TimeoutException {
|
} on TimeoutException {
|
||||||
cleanup();
|
cleanup();
|
||||||
@@ -522,6 +643,49 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Encrypted send/receive ──
|
||||||
|
|
||||||
|
// Serializes async sends to prevent counter interleaving
|
||||||
|
Future<void>? _sendChain;
|
||||||
|
|
||||||
|
Future<List<int>> _encryptOutgoing(String plaintext) async {
|
||||||
|
final encrypted = await RemoteAuthService.instance.encrypt(
|
||||||
|
_sessionEncKey!,
|
||||||
|
utf8.encode(plaintext),
|
||||||
|
isHost: _role == RemoteSessionRole.host,
|
||||||
|
counter: _sendCounter,
|
||||||
|
);
|
||||||
|
_sendCounter++;
|
||||||
|
return encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _sendEncryptedToSocket(WebSocket socket, String plaintext) async {
|
||||||
|
if (_sessionEncKey == null) return;
|
||||||
|
final encrypted = await _encryptOutgoing(plaintext);
|
||||||
|
socket.add(encrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> _decryptIncoming(dynamic data) async {
|
||||||
|
if (_sessionEncKey == null) return null;
|
||||||
|
try {
|
||||||
|
final auth = RemoteAuthService.instance;
|
||||||
|
final bytes = data is List<int> ? data : utf8.encode(data as String);
|
||||||
|
final decrypted = await auth.decrypt(
|
||||||
|
bytes,
|
||||||
|
_sessionEncKey!,
|
||||||
|
fromHost: _role == RemoteSessionRole.remote, // If we're remote, incoming is from host
|
||||||
|
expectedCounter: _recvCounter,
|
||||||
|
);
|
||||||
|
_recvCounter++;
|
||||||
|
return utf8.decode(decrypted);
|
||||||
|
} catch (e) {
|
||||||
|
appLogger.e('CompanionRemote: Decryption failed (counter=$_recvCounter)', error: e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Commands ──
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void sendPing() {
|
void sendPing() {
|
||||||
if (isConnected) {
|
if (isConnected) {
|
||||||
@@ -559,18 +723,23 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sendCommand(RemoteCommand command) {
|
void sendCommand(RemoteCommand command) {
|
||||||
|
if (_sessionEncKey == null || !_isAuthenticated) {
|
||||||
|
appLogger.w('CompanionRemote: No connection to send command');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chain sends to prevent counter interleaving from concurrent async encrypts
|
||||||
|
_sendChain = (_sendChain ?? Future.value()).then((_) async {
|
||||||
try {
|
try {
|
||||||
final json = jsonEncode(command.toJson());
|
final json = jsonEncode(command.toJson());
|
||||||
|
final encrypted = await _encryptOutgoing(json);
|
||||||
|
|
||||||
if (_role == RemoteSessionRole.host && _clientSocket != null) {
|
if (_role == RemoteSessionRole.host && _clientSocket != null) {
|
||||||
_clientSocket!.add(json);
|
_clientSocket!.add(encrypted);
|
||||||
appLogger.d('CompanionRemote: Sent command (host): ${command.type}');
|
|
||||||
} else if (_role == RemoteSessionRole.remote && _channel != null) {
|
} else if (_role == RemoteSessionRole.remote && _channel != null) {
|
||||||
_channel!.sink.add(json);
|
_channel!.sink.add(encrypted);
|
||||||
appLogger.d('CompanionRemote: Sent command (remote): ${command.type}');
|
|
||||||
} else {
|
|
||||||
appLogger.w('CompanionRemote: No connection to send command');
|
|
||||||
}
|
}
|
||||||
|
appLogger.d('CompanionRemote: Sent command: ${command.type}');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
appLogger.e('CompanionRemote: Failed to send command', error: e);
|
appLogger.e('CompanionRemote: Failed to send command', error: e);
|
||||||
_errorController.add(
|
_errorController.add(
|
||||||
@@ -581,6 +750,7 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> disconnect() async {
|
Future<void> disconnect() async {
|
||||||
@@ -607,15 +777,23 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
|||||||
_server = null;
|
_server = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_sessionId = null;
|
|
||||||
_pin = null;
|
|
||||||
_myPeerId = null;
|
_myPeerId = null;
|
||||||
_hostAddress = null;
|
_hostAddress = null;
|
||||||
_role = null;
|
_role = null;
|
||||||
|
_sessionEncKey = null;
|
||||||
|
_sendCounter = 0;
|
||||||
|
_recvCounter = 0;
|
||||||
|
_isAuthenticated = false;
|
||||||
|
_sendChain = null;
|
||||||
|
_failedAuthAttempts.clear();
|
||||||
|
_authLockouts.clear();
|
||||||
|
|
||||||
_connectionStateController.add(RemoteSessionStatus.disconnected);
|
_connectionStateController.add(RemoteSessionStatus.disconnected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the HTTP server is currently running.
|
||||||
|
bool get isServerRunning => _server != null;
|
||||||
|
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
await disconnect();
|
await disconnect();
|
||||||
await _commandReceivedController.close();
|
await _commandReceivedController.close();
|
||||||
|
|||||||
@@ -0,0 +1,284 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import '../../utils/app_logger.dart';
|
||||||
|
import 'remote_auth_service.dart';
|
||||||
|
|
||||||
|
/// A host discovered on the LAN via UDP broadcast.
|
||||||
|
class DiscoveredHost {
|
||||||
|
final String clientId;
|
||||||
|
final String name;
|
||||||
|
final String platform;
|
||||||
|
final int port;
|
||||||
|
final List<String> ips;
|
||||||
|
DateTime lastSeen;
|
||||||
|
|
||||||
|
DiscoveredHost({
|
||||||
|
required this.clientId,
|
||||||
|
required this.name,
|
||||||
|
required this.platform,
|
||||||
|
required this.port,
|
||||||
|
required this.ips,
|
||||||
|
DateTime? lastSeen,
|
||||||
|
}) : lastSeen = lastSeen ?? DateTime.now();
|
||||||
|
|
||||||
|
List<String> get addresses => ips.map((ip) => '$ip:$port').toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// UDP-based LAN discovery for companion remote.
|
||||||
|
///
|
||||||
|
/// Hosts broadcast authenticated beacons; clients listen and filter
|
||||||
|
/// by matching Plex home membership.
|
||||||
|
class LanDiscoveryService {
|
||||||
|
static const int discoveryPort = 48633;
|
||||||
|
static const int _broadcastIntervalSeconds = 3;
|
||||||
|
static const int _staleTimeoutSeconds = 10;
|
||||||
|
static const int _beaconVersion = 1;
|
||||||
|
|
||||||
|
// Broadcaster state (host)
|
||||||
|
RawDatagramSocket? _broadcastSocket;
|
||||||
|
Timer? _broadcastTimer;
|
||||||
|
|
||||||
|
// Listener state (client)
|
||||||
|
RawDatagramSocket? _listenSocket;
|
||||||
|
Timer? _staleCleanupTimer;
|
||||||
|
final Map<String, DiscoveredHost> _discoveredHosts = {};
|
||||||
|
final _hostsController = StreamController<List<DiscoveredHost>>.broadcast();
|
||||||
|
|
||||||
|
/// Whether the broadcaster is currently active.
|
||||||
|
bool get isBroadcasting => _broadcastTimer != null;
|
||||||
|
|
||||||
|
/// Whether the listener is currently active.
|
||||||
|
bool get isListening => _listenSocket != null;
|
||||||
|
|
||||||
|
// ── Host: Broadcasting ──
|
||||||
|
|
||||||
|
Future<void> startBroadcasting({
|
||||||
|
required List<int> discoveryKey,
|
||||||
|
required String deviceName,
|
||||||
|
required String platform,
|
||||||
|
required String clientId,
|
||||||
|
required int wsPort,
|
||||||
|
required List<String> ips,
|
||||||
|
}) async {
|
||||||
|
await stopBroadcasting();
|
||||||
|
|
||||||
|
try {
|
||||||
|
_broadcastSocket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, 0);
|
||||||
|
_broadcastSocket!.broadcastEnabled = true;
|
||||||
|
|
||||||
|
appLogger.d('LanDiscovery: Broadcasting started on port $discoveryPort');
|
||||||
|
|
||||||
|
// Send immediately, then periodically
|
||||||
|
_sendBeacon(discoveryKey, deviceName, platform, clientId, wsPort, ips);
|
||||||
|
_broadcastTimer = Timer.periodic(
|
||||||
|
const Duration(seconds: _broadcastIntervalSeconds),
|
||||||
|
(_) => _sendBeacon(discoveryKey, deviceName, platform, clientId, wsPort, ips),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
appLogger.e('LanDiscovery: Failed to start broadcasting', error: e);
|
||||||
|
await stopBroadcasting();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _sendBeacon(
|
||||||
|
List<int> discoveryKey,
|
||||||
|
String deviceName,
|
||||||
|
String platform,
|
||||||
|
String clientId,
|
||||||
|
int wsPort,
|
||||||
|
List<String> ips,
|
||||||
|
) {
|
||||||
|
if (_broadcastSocket == null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final auth = RemoteAuthService.instance;
|
||||||
|
final homeHash = auth.computeDiscoveryTag(discoveryKey);
|
||||||
|
|
||||||
|
final beaconHmac = auth.computeBeaconHmac(
|
||||||
|
discoveryKey: discoveryKey,
|
||||||
|
version: _beaconVersion,
|
||||||
|
homeHash: homeHash,
|
||||||
|
name: deviceName,
|
||||||
|
platform: platform,
|
||||||
|
clientId: clientId,
|
||||||
|
port: wsPort,
|
||||||
|
ips: ips,
|
||||||
|
);
|
||||||
|
|
||||||
|
final packet = jsonEncode({
|
||||||
|
'app': 'plezy',
|
||||||
|
'v': _beaconVersion,
|
||||||
|
'homeHash': homeHash,
|
||||||
|
'name': deviceName,
|
||||||
|
'platform': platform,
|
||||||
|
'clientId': clientId,
|
||||||
|
'port': wsPort,
|
||||||
|
'ips': ips,
|
||||||
|
'hmac': beaconHmac,
|
||||||
|
});
|
||||||
|
|
||||||
|
final data = utf8.encode(packet);
|
||||||
|
_broadcastSocket!.send(data, InternetAddress('255.255.255.255'), discoveryPort);
|
||||||
|
} catch (e) {
|
||||||
|
appLogger.e('LanDiscovery: Failed to send beacon', error: e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> stopBroadcasting() async {
|
||||||
|
_broadcastTimer?.cancel();
|
||||||
|
_broadcastTimer = null;
|
||||||
|
_broadcastSocket?.close();
|
||||||
|
_broadcastSocket = null;
|
||||||
|
appLogger.d('LanDiscovery: Broadcasting stopped');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Client: Listening ──
|
||||||
|
|
||||||
|
/// Start listening for host beacons.
|
||||||
|
/// Returns a stream of currently-visible hosts, updated on each beacon or stale cleanup.
|
||||||
|
Stream<List<DiscoveredHost>> startListening({
|
||||||
|
required List<int> discoveryKey,
|
||||||
|
}) {
|
||||||
|
_stopListeningInternal();
|
||||||
|
_discoveredHosts.clear();
|
||||||
|
|
||||||
|
_bindListener(discoveryKey);
|
||||||
|
|
||||||
|
// Periodically remove stale hosts
|
||||||
|
_staleCleanupTimer = Timer.periodic(const Duration(seconds: 2), (_) {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final staleIds = <String>[];
|
||||||
|
for (final entry in _discoveredHosts.entries) {
|
||||||
|
if (now.difference(entry.value.lastSeen).inSeconds > _staleTimeoutSeconds) {
|
||||||
|
staleIds.add(entry.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (staleIds.isNotEmpty) {
|
||||||
|
for (final id in staleIds) {
|
||||||
|
_discoveredHosts.remove(id);
|
||||||
|
}
|
||||||
|
_emitHosts();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return _hostsController.stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _bindListener(List<int> discoveryKey) async {
|
||||||
|
try {
|
||||||
|
_listenSocket = await RawDatagramSocket.bind(
|
||||||
|
InternetAddress.anyIPv4,
|
||||||
|
discoveryPort,
|
||||||
|
reuseAddress: true,
|
||||||
|
reusePort: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
appLogger.d('LanDiscovery: Listening on port $discoveryPort');
|
||||||
|
|
||||||
|
_listenSocket!.listen((RawSocketEvent event) {
|
||||||
|
if (event == RawSocketEvent.read) {
|
||||||
|
final datagram = _listenSocket?.receive();
|
||||||
|
if (datagram != null) {
|
||||||
|
_handleDatagram(datagram, discoveryKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
appLogger.e('LanDiscovery: Failed to bind listener', error: e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleDatagram(Datagram datagram, List<int> discoveryKey) {
|
||||||
|
try {
|
||||||
|
final packet = utf8.decode(datagram.data);
|
||||||
|
final json = jsonDecode(packet) as Map<String, dynamic>;
|
||||||
|
|
||||||
|
if (json['app'] != 'plezy') return;
|
||||||
|
|
||||||
|
final version = json['v'] as int? ?? 0;
|
||||||
|
final homeHash = json['homeHash'] as String? ?? '';
|
||||||
|
final name = json['name'] as String? ?? '';
|
||||||
|
final platform = json['platform'] as String? ?? '';
|
||||||
|
final clientId = json['clientId'] as String? ?? '';
|
||||||
|
final port = json['port'] as int? ?? 0;
|
||||||
|
final ips = (json['ips'] as List<dynamic>?)?.cast<String>() ?? [];
|
||||||
|
final hmac = json['hmac'] as String? ?? '';
|
||||||
|
|
||||||
|
// Verify beacon HMAC
|
||||||
|
final auth = RemoteAuthService.instance;
|
||||||
|
if (!auth.verifyBeaconHmac(
|
||||||
|
receivedHmac: hmac,
|
||||||
|
discoveryKey: discoveryKey,
|
||||||
|
version: version,
|
||||||
|
homeHash: homeHash,
|
||||||
|
name: name,
|
||||||
|
platform: platform,
|
||||||
|
clientId: clientId,
|
||||||
|
port: port,
|
||||||
|
ips: ips,
|
||||||
|
)) {
|
||||||
|
return; // Invalid HMAC — not from same home or tampered
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify homeHash matches (check ±1 epoch window)
|
||||||
|
if (!auth.matchesDiscoveryTag(homeHash, discoveryKey)) {
|
||||||
|
return; // Different home
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid beacon from same home
|
||||||
|
if (_discoveredHosts.containsKey(clientId)) {
|
||||||
|
final existing = _discoveredHosts[clientId]!;
|
||||||
|
existing.lastSeen = DateTime.now();
|
||||||
|
// Only emit if fields actually changed
|
||||||
|
if (existing.name != name || existing.port != port) {
|
||||||
|
_discoveredHosts[clientId] = DiscoveredHost(
|
||||||
|
clientId: clientId,
|
||||||
|
name: name,
|
||||||
|
platform: platform,
|
||||||
|
port: port,
|
||||||
|
ips: ips,
|
||||||
|
);
|
||||||
|
_emitHosts();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_discoveredHosts[clientId] = DiscoveredHost(
|
||||||
|
clientId: clientId,
|
||||||
|
name: name,
|
||||||
|
platform: platform,
|
||||||
|
port: port,
|
||||||
|
ips: ips,
|
||||||
|
);
|
||||||
|
appLogger.d('LanDiscovery: Discovered host: $name ($platform) at ${ips.join(", ")}:$port');
|
||||||
|
_emitHosts();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore malformed packets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _emitHosts() {
|
||||||
|
_hostsController.add(_discoveredHosts.values.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopListening() {
|
||||||
|
_stopListeningInternal();
|
||||||
|
_discoveredHosts.clear();
|
||||||
|
_emitHosts();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _stopListeningInternal() {
|
||||||
|
_staleCleanupTimer?.cancel();
|
||||||
|
_staleCleanupTimer = null;
|
||||||
|
_listenSocket?.close();
|
||||||
|
_listenSocket = null;
|
||||||
|
appLogger.d('LanDiscovery: Listening stopped');
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
stopBroadcasting();
|
||||||
|
stopListening();
|
||||||
|
_hostsController.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,384 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:crypto/crypto.dart' as crypto;
|
||||||
|
import 'package:cryptography/cryptography.dart';
|
||||||
|
|
||||||
|
import '../../models/plex_home.dart';
|
||||||
|
import '../../utils/app_logger.dart';
|
||||||
|
|
||||||
|
/// Cryptographic authentication service for companion remote.
|
||||||
|
///
|
||||||
|
/// Proves same-home membership via a shared secret derived from Plex home data.
|
||||||
|
/// This authenticates **group membership**, not individual user identity —
|
||||||
|
/// any device in the same Plex home can connect.
|
||||||
|
class RemoteAuthService {
|
||||||
|
RemoteAuthService._();
|
||||||
|
static final instance = RemoteAuthService._();
|
||||||
|
|
||||||
|
// Reusable crypto algorithm instances
|
||||||
|
static final _hkdf = Hkdf(hmac: Hmac(Sha256()), outputLength: 32);
|
||||||
|
static final _aesGcm = AesGcm.with256bits();
|
||||||
|
|
||||||
|
// Cached home secret — derived in memory, never persisted
|
||||||
|
List<int>? _cachedHomeSecret;
|
||||||
|
int? _cachedHomeId;
|
||||||
|
String? _cachedAdminUUID;
|
||||||
|
|
||||||
|
/// Build canonical IKM bytes from home data.
|
||||||
|
/// Format: [4-byte BE len][utf8 bytes] for each field, in fixed order.
|
||||||
|
static Uint8List _canonicalIkm(int homeId, String adminUUID) {
|
||||||
|
final homeIdBytes = utf8.encode(homeId.toString());
|
||||||
|
final uuidBytes = utf8.encode(adminUUID.toLowerCase());
|
||||||
|
|
||||||
|
final buf = BytesWriter();
|
||||||
|
buf.writeUint32BE(homeIdBytes.length);
|
||||||
|
buf.writeBytes(homeIdBytes);
|
||||||
|
buf.writeUint32BE(uuidBytes.length);
|
||||||
|
buf.writeBytes(uuidBytes);
|
||||||
|
return buf.toBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derive the long-term home secret via HKDF-SHA256.
|
||||||
|
/// This is derived in memory from cached Plex data — never persisted.
|
||||||
|
Future<List<int>> deriveHomeSecret(int homeId, String adminUUID) async {
|
||||||
|
// Return cached if inputs unchanged
|
||||||
|
if (_cachedHomeSecret != null && _cachedHomeId == homeId && _cachedAdminUUID == adminUUID) {
|
||||||
|
return _cachedHomeSecret!;
|
||||||
|
}
|
||||||
|
|
||||||
|
final hkdf = _hkdf;
|
||||||
|
final ikm = _canonicalIkm(homeId, adminUUID);
|
||||||
|
|
||||||
|
final secretKey = await hkdf.deriveKey(
|
||||||
|
secretKey: SecretKey(ikm),
|
||||||
|
nonce: utf8.encode('plezy-remote-v1'),
|
||||||
|
info: utf8.encode('home-secret'),
|
||||||
|
);
|
||||||
|
|
||||||
|
_cachedHomeSecret = await secretKey.extractBytes();
|
||||||
|
_cachedHomeId = homeId;
|
||||||
|
_cachedAdminUUID = adminUUID;
|
||||||
|
|
||||||
|
appLogger.d('RemoteAuth: Derived home secret');
|
||||||
|
return _cachedHomeSecret!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derive the long-term home secret from a PlexHome object.
|
||||||
|
Future<List<int>> deriveHomeSecretFromHome(PlexHome home) async {
|
||||||
|
final admin = home.adminUser;
|
||||||
|
if (admin == null) {
|
||||||
|
throw StateError('PlexHome has no admin user');
|
||||||
|
}
|
||||||
|
return deriveHomeSecret(home.id, admin.uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derive per-session encryption key from homeSecret + both nonces.
|
||||||
|
Future<List<int>> deriveSessionEncKey(
|
||||||
|
List<int> homeSecret,
|
||||||
|
List<int> hostNonce,
|
||||||
|
List<int> clientNonce,
|
||||||
|
) async {
|
||||||
|
final hkdf = _hkdf;
|
||||||
|
|
||||||
|
final salt = Uint8List(hostNonce.length + clientNonce.length);
|
||||||
|
salt.setAll(0, hostNonce);
|
||||||
|
salt.setAll(hostNonce.length, clientNonce);
|
||||||
|
|
||||||
|
// First derive session secret
|
||||||
|
final sessionSecretKey = await hkdf.deriveKey(
|
||||||
|
secretKey: SecretKey(homeSecret),
|
||||||
|
nonce: salt,
|
||||||
|
info: utf8.encode('plezy-session-v1'),
|
||||||
|
);
|
||||||
|
final sessionSecret = await sessionSecretKey.extractBytes();
|
||||||
|
|
||||||
|
// Then derive encryption key from session secret
|
||||||
|
final encKeyResult = await hkdf.deriveKey(
|
||||||
|
secretKey: SecretKey(sessionSecret),
|
||||||
|
nonce: const <int>[],
|
||||||
|
info: utf8.encode('encryption'),
|
||||||
|
);
|
||||||
|
|
||||||
|
return encKeyResult.extractBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Derive discovery key from homeSecret.
|
||||||
|
Future<List<int>> deriveDiscoveryKey(List<int> homeSecret) async {
|
||||||
|
final hkdf = _hkdf;
|
||||||
|
final key = await hkdf.deriveKey(
|
||||||
|
secretKey: SecretKey(homeSecret),
|
||||||
|
nonce: const <int>[],
|
||||||
|
info: utf8.encode('discovery'),
|
||||||
|
);
|
||||||
|
return key.extractBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute rotating discovery tag for beacon filtering.
|
||||||
|
/// Uses 5-minute epoch windows to reduce cross-network tracking.
|
||||||
|
String computeDiscoveryTag(List<int> discoveryKey, {DateTime? now}) {
|
||||||
|
final epochSeconds = ((now ?? DateTime.now()).millisecondsSinceEpoch ~/ 1000);
|
||||||
|
final window = epochSeconds ~/ 300; // 5-minute windows
|
||||||
|
final msg = utf8.encode('identify|$window');
|
||||||
|
final hmac = crypto.Hmac(crypto.sha256, discoveryKey);
|
||||||
|
return hmac.convert(msg).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a received homeHash matches any of the ±1 epoch windows.
|
||||||
|
bool matchesDiscoveryTag(String receivedHash, List<int> discoveryKey, {DateTime? now}) {
|
||||||
|
final epochSeconds = ((now ?? DateTime.now()).millisecondsSinceEpoch ~/ 1000);
|
||||||
|
final currentWindow = epochSeconds ~/ 300;
|
||||||
|
|
||||||
|
for (final window in [currentWindow - 1, currentWindow, currentWindow + 1]) {
|
||||||
|
final msg = utf8.encode('identify|$window');
|
||||||
|
final hmac = crypto.Hmac(crypto.sha256, discoveryKey);
|
||||||
|
final tag = hmac.convert(msg).toString();
|
||||||
|
if (_constantTimeEquals(tag, receivedHash)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate 32 cryptographically random bytes.
|
||||||
|
List<int> generateNonce() {
|
||||||
|
final random = Random.secure();
|
||||||
|
return List<int>.generate(32, (_) => random.nextInt(256));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute auth tag (HMAC-SHA256) over the full handshake transcript.
|
||||||
|
/// Domain-separated with "plezy-auth-v1|" prefix.
|
||||||
|
String computeAuthTag({
|
||||||
|
required List<int> homeSecret,
|
||||||
|
required List<int> hostNonce,
|
||||||
|
required List<int> clientNonce,
|
||||||
|
required String hostClientId,
|
||||||
|
required String userUUID,
|
||||||
|
required String clientIdentifier,
|
||||||
|
required String deviceName,
|
||||||
|
required String platform,
|
||||||
|
}) {
|
||||||
|
final msg = _buildAuthTranscript(
|
||||||
|
hostNonce: hostNonce,
|
||||||
|
clientNonce: clientNonce,
|
||||||
|
hostClientId: hostClientId,
|
||||||
|
userUUID: userUUID,
|
||||||
|
clientIdentifier: clientIdentifier,
|
||||||
|
deviceName: deviceName,
|
||||||
|
platform: platform,
|
||||||
|
);
|
||||||
|
final hmac = crypto.Hmac(crypto.sha256, homeSecret);
|
||||||
|
return hmac.convert(msg).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify auth tag with constant-time comparison.
|
||||||
|
bool verifyAuthTag({
|
||||||
|
required String authTag,
|
||||||
|
required List<int> homeSecret,
|
||||||
|
required List<int> hostNonce,
|
||||||
|
required List<int> clientNonce,
|
||||||
|
required String hostClientId,
|
||||||
|
required String userUUID,
|
||||||
|
required String clientIdentifier,
|
||||||
|
required String deviceName,
|
||||||
|
required String platform,
|
||||||
|
}) {
|
||||||
|
final expected = computeAuthTag(
|
||||||
|
homeSecret: homeSecret,
|
||||||
|
hostNonce: hostNonce,
|
||||||
|
clientNonce: clientNonce,
|
||||||
|
hostClientId: hostClientId,
|
||||||
|
userUUID: userUUID,
|
||||||
|
clientIdentifier: clientIdentifier,
|
||||||
|
deviceName: deviceName,
|
||||||
|
platform: platform,
|
||||||
|
);
|
||||||
|
return _constantTimeEquals(expected, authTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the auth transcript message bytes.
|
||||||
|
List<int> _buildAuthTranscript({
|
||||||
|
required List<int> hostNonce,
|
||||||
|
required List<int> clientNonce,
|
||||||
|
required String hostClientId,
|
||||||
|
required String userUUID,
|
||||||
|
required String clientIdentifier,
|
||||||
|
required String deviceName,
|
||||||
|
required String platform,
|
||||||
|
}) {
|
||||||
|
final buf = BytesWriter();
|
||||||
|
buf.writeBytes(utf8.encode('plezy-auth-v1|'));
|
||||||
|
// Fixed-length nonces (32 bytes each) — no prefix needed
|
||||||
|
buf.writeBytes(hostNonce);
|
||||||
|
buf.writeBytes(clientNonce);
|
||||||
|
// Variable-length strings — length-prefixed to prevent field boundary shifting
|
||||||
|
_writeLengthPrefixed(buf, utf8.encode(hostClientId));
|
||||||
|
_writeLengthPrefixed(buf, utf8.encode(userUUID));
|
||||||
|
_writeLengthPrefixed(buf, utf8.encode(clientIdentifier));
|
||||||
|
_writeLengthPrefixed(buf, utf8.encode(deviceName));
|
||||||
|
_writeLengthPrefixed(buf, utf8.encode(platform));
|
||||||
|
return buf.toBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute HMAC for discovery beacon over canonical binary layout.
|
||||||
|
/// Fields in fixed order: v, homeHash, name, platform, clientId, port, ips (sorted, comma-joined).
|
||||||
|
String computeBeaconHmac({
|
||||||
|
required List<int> discoveryKey,
|
||||||
|
required int version,
|
||||||
|
required String homeHash,
|
||||||
|
required String name,
|
||||||
|
required String platform,
|
||||||
|
required String clientId,
|
||||||
|
required int port,
|
||||||
|
required List<String> ips,
|
||||||
|
}) {
|
||||||
|
final buf = BytesWriter();
|
||||||
|
_writeLengthPrefixed(buf, utf8.encode(version.toString()));
|
||||||
|
_writeLengthPrefixed(buf, utf8.encode(homeHash));
|
||||||
|
_writeLengthPrefixed(buf, utf8.encode(name));
|
||||||
|
_writeLengthPrefixed(buf, utf8.encode(platform));
|
||||||
|
_writeLengthPrefixed(buf, utf8.encode(clientId));
|
||||||
|
_writeLengthPrefixed(buf, utf8.encode(port.toString()));
|
||||||
|
final sortedIps = List<String>.from(ips)..sort();
|
||||||
|
_writeLengthPrefixed(buf, utf8.encode(sortedIps.join(',')));
|
||||||
|
|
||||||
|
final hmac = crypto.Hmac(crypto.sha256, discoveryKey);
|
||||||
|
return hmac.convert(buf.toBytes()).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify a beacon's HMAC.
|
||||||
|
bool verifyBeaconHmac({
|
||||||
|
required String receivedHmac,
|
||||||
|
required List<int> discoveryKey,
|
||||||
|
required int version,
|
||||||
|
required String homeHash,
|
||||||
|
required String name,
|
||||||
|
required String platform,
|
||||||
|
required String clientId,
|
||||||
|
required int port,
|
||||||
|
required List<String> ips,
|
||||||
|
}) {
|
||||||
|
final expected = computeBeaconHmac(
|
||||||
|
discoveryKey: discoveryKey,
|
||||||
|
version: version,
|
||||||
|
homeHash: homeHash,
|
||||||
|
name: name,
|
||||||
|
platform: platform,
|
||||||
|
clientId: clientId,
|
||||||
|
port: port,
|
||||||
|
ips: ips,
|
||||||
|
);
|
||||||
|
return _constantTimeEquals(expected, receivedHmac);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── AES-256-GCM Encryption ──
|
||||||
|
|
||||||
|
static const int _directionHost = 0x01;
|
||||||
|
static const int _directionClient = 0x02;
|
||||||
|
|
||||||
|
/// Build the 12-byte GCM nonce from direction + counter.
|
||||||
|
static Uint8List buildNonce(int direction, int counter) {
|
||||||
|
final nonce = Uint8List(12);
|
||||||
|
// 4-byte direction (big-endian)
|
||||||
|
nonce[0] = (direction >> 24) & 0xFF;
|
||||||
|
nonce[1] = (direction >> 16) & 0xFF;
|
||||||
|
nonce[2] = (direction >> 8) & 0xFF;
|
||||||
|
nonce[3] = direction & 0xFF;
|
||||||
|
// 8-byte counter (big-endian)
|
||||||
|
nonce[4] = (counter >> 56) & 0xFF;
|
||||||
|
nonce[5] = (counter >> 48) & 0xFF;
|
||||||
|
nonce[6] = (counter >> 40) & 0xFF;
|
||||||
|
nonce[7] = (counter >> 32) & 0xFF;
|
||||||
|
nonce[8] = (counter >> 24) & 0xFF;
|
||||||
|
nonce[9] = (counter >> 16) & 0xFF;
|
||||||
|
nonce[10] = (counter >> 8) & 0xFF;
|
||||||
|
nonce[11] = counter & 0xFF;
|
||||||
|
return nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypt plaintext with AES-256-GCM.
|
||||||
|
/// Returns ciphertext + auth tag (nonce is implicit from counters).
|
||||||
|
Future<List<int>> encrypt(
|
||||||
|
List<int> sessionEncKey,
|
||||||
|
List<int> plaintext, {
|
||||||
|
required bool isHost,
|
||||||
|
required int counter,
|
||||||
|
}) async {
|
||||||
|
final algo = _aesGcm;
|
||||||
|
final nonce = buildNonce(isHost ? _directionHost : _directionClient, counter);
|
||||||
|
|
||||||
|
final secretBox = await algo.encrypt(
|
||||||
|
plaintext,
|
||||||
|
secretKey: SecretKey(sessionEncKey),
|
||||||
|
nonce: nonce,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return ciphertext + mac (nonce is implicit)
|
||||||
|
return [...secretBox.cipherText, ...secretBox.mac.bytes];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decrypt ciphertext + auth tag with AES-256-GCM.
|
||||||
|
/// Receiver reconstructs the nonce from its own expected counter.
|
||||||
|
Future<List<int>> decrypt(
|
||||||
|
List<int> data,
|
||||||
|
List<int> sessionEncKey, {
|
||||||
|
required bool fromHost,
|
||||||
|
required int expectedCounter,
|
||||||
|
}) async {
|
||||||
|
final algo = _aesGcm;
|
||||||
|
final nonce = buildNonce(fromHost ? _directionHost : _directionClient, expectedCounter);
|
||||||
|
|
||||||
|
if (data.length < 16) {
|
||||||
|
throw ArgumentError('Encrypted data too short');
|
||||||
|
}
|
||||||
|
|
||||||
|
final cipherText = data.sublist(0, data.length - 16);
|
||||||
|
final mac = Mac(data.sublist(data.length - 16));
|
||||||
|
|
||||||
|
final secretBox = SecretBox(cipherText, nonce: nonce, mac: mac);
|
||||||
|
return algo.decrypt(secretBox, secretKey: SecretKey(sessionEncKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Utility ──
|
||||||
|
|
||||||
|
static void _writeLengthPrefixed(BytesWriter buf, List<int> bytes) {
|
||||||
|
buf.writeUint32BE(bytes.length);
|
||||||
|
buf.writeBytes(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Constant-time string comparison to prevent timing attacks.
|
||||||
|
static bool _constantTimeEquals(String a, String b) {
|
||||||
|
if (a.length != b.length) return false;
|
||||||
|
var result = 0;
|
||||||
|
for (var i = 0; i < a.length; i++) {
|
||||||
|
result |= a.codeUnitAt(i) ^ b.codeUnitAt(i);
|
||||||
|
}
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear cached home secret (e.g. on logout).
|
||||||
|
void clearCache() {
|
||||||
|
_cachedHomeSecret = null;
|
||||||
|
_cachedHomeId = null;
|
||||||
|
_cachedAdminUUID = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static direction constants for external use
|
||||||
|
static int get directionHost => _directionHost;
|
||||||
|
static int get directionClient => _directionClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper for building byte arrays.
|
||||||
|
class BytesWriter {
|
||||||
|
final _bytes = <int>[];
|
||||||
|
|
||||||
|
void writeBytes(List<int> data) => _bytes.addAll(data);
|
||||||
|
|
||||||
|
void writeUint32BE(int value) {
|
||||||
|
_bytes.add((value >> 24) & 0xFF);
|
||||||
|
_bytes.add((value >> 16) & 0xFF);
|
||||||
|
_bytes.add((value >> 8) & 0xFF);
|
||||||
|
_bytes.add(value & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8List toBytes() => Uint8List.fromList(_bytes);
|
||||||
|
}
|
||||||
@@ -92,6 +92,7 @@ class SettingsService extends BaseSharedPreferencesService {
|
|||||||
static const String _keyMpvConfigPresets = 'mpv_config_presets';
|
static const String _keyMpvConfigPresets = 'mpv_config_presets';
|
||||||
static const String _keyMaxVolume = 'max_volume';
|
static const String _keyMaxVolume = 'max_volume';
|
||||||
static const String _keyEnableDiscordRPC = 'enable_discord_rpc';
|
static const String _keyEnableDiscordRPC = 'enable_discord_rpc';
|
||||||
|
static const String _keyEnableCompanionRemoteServer = 'enable_companion_remote_server';
|
||||||
static const String _keyAutoPip = 'auto_pip';
|
static const String _keyAutoPip = 'auto_pip';
|
||||||
static const String _keyMatchContentFrameRate = 'match_content_frame_rate';
|
static const String _keyMatchContentFrameRate = 'match_content_frame_rate';
|
||||||
static const String _keyTunneledPlayback = 'tunneled_playback';
|
static const String _keyTunneledPlayback = 'tunneled_playback';
|
||||||
@@ -1104,6 +1105,17 @@ class SettingsService extends BaseSharedPreferencesService {
|
|||||||
return prefs.getBool(_keyEnableDiscordRPC) ?? false; // Default disabled
|
return prefs.getBool(_keyEnableDiscordRPC) ?? false; // Default disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Companion Remote Server
|
||||||
|
Future<void> setEnableCompanionRemoteServer(bool enabled) async {
|
||||||
|
await prefs.setBool(_keyEnableCompanionRemoteServer, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool getEnableCompanionRemoteServer() {
|
||||||
|
// Default enabled on desktop/TV, disabled on mobile
|
||||||
|
return prefs.getBool(_keyEnableCompanionRemoteServer) ??
|
||||||
|
(Platform.isMacOS || Platform.isWindows || Platform.isLinux);
|
||||||
|
}
|
||||||
|
|
||||||
// Auto Picture-in-Picture (Android & iOS)
|
// Auto Picture-in-Picture (Android & iOS)
|
||||||
Future<void> setAutoPip(bool enabled) async {
|
Future<void> setAutoPip(bool enabled) async {
|
||||||
await prefs.setBool(_keyAutoPip, enabled);
|
await prefs.setBool(_keyAutoPip, enabled);
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
|
||||||
|
|
||||||
import '../../i18n/strings.g.dart';
|
import '../../i18n/strings.g.dart';
|
||||||
import '../../providers/companion_remote_provider.dart';
|
import '../../providers/companion_remote_provider.dart';
|
||||||
|
import '../../providers/user_profile_provider.dart';
|
||||||
import '../../focus/focusable_button.dart';
|
import '../../focus/focusable_button.dart';
|
||||||
import '../../utils/app_logger.dart';
|
import '../../utils/app_logger.dart';
|
||||||
|
|
||||||
@@ -24,58 +23,55 @@ class RemoteSessionDialog extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _RemoteSessionDialogState extends State<RemoteSessionDialog> {
|
class _RemoteSessionDialogState extends State<RemoteSessionDialog> {
|
||||||
bool _isCreatingSession = false;
|
bool _isStarting = false;
|
||||||
String? _errorMessage;
|
String? _errorMessage;
|
||||||
List<String>? _hostAddresses; // Each format: "ip:port"
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
// Defer to avoid notifyListeners() during build phase
|
WidgetsBinding.instance.addPostFrameCallback((_) => _ensureServerRunning());
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => _createSession());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _createSession() async {
|
Future<void> _ensureServerRunning() async {
|
||||||
|
final provider = context.read<CompanionRemoteProvider>();
|
||||||
|
if (provider.isHostServerRunning) return;
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_isCreatingSession = true;
|
_isStarting = true;
|
||||||
_errorMessage = null;
|
_errorMessage = null;
|
||||||
_hostAddresses = null;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final provider = context.read<CompanionRemoteProvider>();
|
final home = context.read<UserProfileProvider>().home;
|
||||||
final result = await provider.createSession();
|
await provider.ensureCryptoReady(home);
|
||||||
|
if (!mounted) return;
|
||||||
|
await provider.startHostServer();
|
||||||
|
|
||||||
if (!mounted) return;
|
if (mounted) setState(() => _isStarting = false);
|
||||||
setState(() {
|
|
||||||
_isCreatingSession = false;
|
|
||||||
_hostAddresses = result.addresses;
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
appLogger.e('Failed to create companion remote session', error: e);
|
appLogger.e('Failed to start companion remote server', error: e);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_isCreatingSession = false;
|
_isStarting = false;
|
||||||
_errorMessage = e.toString();
|
_errorMessage = e.toString();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _copyToClipboard(String text, String label) {
|
Future<void> _toggleServer() async {
|
||||||
Clipboard.setData(ClipboardData(text: text));
|
final provider = context.read<CompanionRemoteProvider>();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
if (provider.isHostServerRunning) {
|
||||||
SnackBar(
|
await provider.stopHostServer();
|
||||||
content: Text(t.companionRemote.session.copiedToClipboard(label: label)),
|
} else {
|
||||||
duration: const Duration(seconds: 2),
|
await _ensureServerRunning();
|
||||||
),
|
}
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Consumer<CompanionRemoteProvider>(
|
return Consumer<CompanionRemoteProvider>(
|
||||||
builder: (context, provider, child) {
|
builder: (context, provider, child) {
|
||||||
if (_isCreatingSession) {
|
if (_isStarting) {
|
||||||
return Dialog(
|
return Dialog(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(32.0),
|
padding: const EdgeInsets.all(32.0),
|
||||||
@@ -84,7 +80,7 @@ class _RemoteSessionDialogState extends State<RemoteSessionDialog> {
|
|||||||
children: [
|
children: [
|
||||||
const CircularProgressIndicator(),
|
const CircularProgressIndicator(),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(t.companionRemote.session.creatingSession, style: Theme.of(context).textTheme.titleMedium),
|
Text(t.companionRemote.session.startingServer, style: Theme.of(context).textTheme.titleMedium),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -105,27 +101,11 @@ class _RemoteSessionDialogState extends State<RemoteSessionDialog> {
|
|||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(onPressed: () => Navigator.of(context).pop(), child: Text(t.common.close)),
|
TextButton(onPressed: () => Navigator.of(context).pop(), child: Text(t.common.close)),
|
||||||
TextButton(onPressed: _createSession, child: Text(t.common.retry)),
|
TextButton(onPressed: _ensureServerRunning, child: Text(t.common.retry)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final session = provider.session;
|
|
||||||
if (session == null || _hostAddresses == null || _hostAddresses!.isEmpty) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: Text(t.common.error),
|
|
||||||
content: Text(t.companionRemote.session.noSession),
|
|
||||||
actions: [TextButton(onPressed: () => Navigator.of(context).pop(), child: Text(t.common.close))],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract port from first address (all share the same port)
|
|
||||||
final port = _hostAddresses!.first.split(':').last;
|
|
||||||
final ips = _hostAddresses!.map((a) => a.split(':').first).join(',');
|
|
||||||
|
|
||||||
// URL-wrapped QR format: comma-separated IPs for multi-NIC support
|
|
||||||
final qrData = 'https://plezy.app/scan#$ips|$port|${session.sessionId}|${session.pin}';
|
|
||||||
|
|
||||||
return Dialog(
|
return Dialog(
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 500),
|
constraints: const BoxConstraints(maxWidth: 500),
|
||||||
@@ -145,16 +125,7 @@ class _RemoteSessionDialogState extends State<RemoteSessionDialog> {
|
|||||||
children: [
|
children: [
|
||||||
Text(t.companionRemote.title, style: Theme.of(context).textTheme.headlineSmall),
|
Text(t.companionRemote.title, style: Theme.of(context).textTheme.headlineSmall),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
_buildStatusLine(context, provider),
|
||||||
session.connectedDevice != null
|
|
||||||
? t.companionRemote.connectedTo(name: session.connectedDevice!.name)
|
|
||||||
: t.companionRemote.session.waitingForConnection,
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
||||||
color: session.connectedDevice != null
|
|
||||||
? Colors.green
|
|
||||||
: Theme.of(context).textTheme.bodySmall?.color,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -162,113 +133,27 @@ class _RemoteSessionDialogState extends State<RemoteSessionDialog> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
if (session.connectedDevice == null) ...[
|
|
||||||
Text(
|
// Server status card
|
||||||
t.companionRemote.session.scanQrCode,
|
_buildServerStatus(context, provider),
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
textAlign: TextAlign.center,
|
// Connected device info
|
||||||
),
|
if (provider.connectedDevice != null) ...[
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Center(
|
_buildConnectedDevice(context, provider),
|
||||||
child: Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Colors.white,
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
|
||||||
),
|
|
||||||
child: QrImageView(
|
|
||||||
data: qrData,
|
|
||||||
version: QrVersions.auto,
|
|
||||||
size: 200,
|
|
||||||
backgroundColor: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
const Divider(),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
t.companionRemote.session.orEnterManually,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
..._hostAddresses!.map(
|
|
||||||
(addr) => Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 12),
|
|
||||||
child: _buildCodeCard(
|
|
||||||
context,
|
|
||||||
t.companionRemote.session.hostAddress,
|
|
||||||
addr,
|
|
||||||
onCopy: () => _copyToClipboard(addr, t.companionRemote.session.hostAddress),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_buildCodeCard(
|
|
||||||
context,
|
|
||||||
t.companionRemote.session.sessionId,
|
|
||||||
session.sessionId,
|
|
||||||
onCopy: () => _copyToClipboard(session.sessionId, t.companionRemote.session.sessionId),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
_buildCodeCard(
|
|
||||||
context,
|
|
||||||
t.companionRemote.session.pin,
|
|
||||||
session.pin,
|
|
||||||
onCopy: () => _copyToClipboard(session.pin, t.companionRemote.session.pin),
|
|
||||||
),
|
|
||||||
] else ...[
|
|
||||||
Card(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.check_circle, color: Colors.green, size: 48),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(t.companionRemote.session.connected, style: Theme.of(context).textTheme.titleLarge),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Text(session.connectedDevice!.name, style: Theme.of(context).textTheme.bodyLarge),
|
|
||||||
Text(session.connectedDevice!.platform, style: Theme.of(context).textTheme.bodySmall),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
Text(
|
|
||||||
t.companionRemote.session.usePhoneToControl,
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
|
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
if (session.connectedDevice != null)
|
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: () async {
|
onPressed: _toggleServer,
|
||||||
await provider.leaveSession();
|
icon: Icon(provider.isHostServerRunning ? Icons.stop : Icons.play_arrow),
|
||||||
await _createSession();
|
label: Text(
|
||||||
},
|
provider.isHostServerRunning
|
||||||
icon: const Icon(Icons.refresh),
|
? t.companionRemote.session.stopServer
|
||||||
label: Text(t.companionRemote.session.newSession),
|
: t.companionRemote.session.startServer,
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
FocusableButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await provider.leaveSession();
|
|
||||||
if (context.mounted) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: TextButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await provider.leaveSession();
|
|
||||||
if (context.mounted) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Text(t.common.disconnect),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
@@ -290,29 +175,88 @@ class _RemoteSessionDialogState extends State<RemoteSessionDialog> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCodeCard(BuildContext context, String label, String code, {VoidCallback? onCopy}) {
|
Widget _buildStatusLine(BuildContext context, CompanionRemoteProvider provider) {
|
||||||
|
if (provider.connectedDevice != null) {
|
||||||
|
return Text(
|
||||||
|
t.companionRemote.connectedTo(name: provider.connectedDevice!.name),
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.green),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (provider.isHostServerRunning) {
|
||||||
|
return Text(
|
||||||
|
t.companionRemote.session.serverRunning,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Theme.of(context).textTheme.bodySmall?.color,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Text(
|
||||||
|
t.companionRemote.session.serverStopped,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(color: Colors.grey),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildServerStatus(BuildContext context, CompanionRemoteProvider provider) {
|
||||||
|
final isRunning = provider.isHostServerRunning;
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: isRunning ? Colors.green : Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(label, style: Theme.of(context).textTheme.bodySmall),
|
Text(
|
||||||
|
isRunning
|
||||||
|
? t.companionRemote.session.serverRunning
|
||||||
|
: t.companionRemote.session.serverStopped,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
code,
|
isRunning
|
||||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(fontFamily: 'monospace', letterSpacing: 2),
|
? t.companionRemote.session.serverRunningDescription
|
||||||
|
: t.companionRemote.session.serverStoppedDescription,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
],
|
||||||
icon: const Icon(Icons.copy),
|
),
|
||||||
onPressed: onCopy,
|
),
|
||||||
tooltip: t.companionRemote.session.copyToClipboard,
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildConnectedDevice(BuildContext context, CompanionRemoteProvider provider) {
|
||||||
|
final device = provider.connectedDevice!;
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.check_circle, color: Colors.green, size: 48),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(t.companionRemote.session.connected, style: Theme.of(context).textTheme.titleLarge),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(device.name, style: Theme.of(context).textTheme.bodyLarge),
|
||||||
|
Text(device.platform, style: Theme.of(context).textTheme.bodySmall),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
t.companionRemote.session.usePhoneToControl,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
+1
-9
@@ -286,7 +286,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.7"
|
version: "3.0.7"
|
||||||
cryptography:
|
cryptography:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: cryptography
|
name: cryptography
|
||||||
sha256: "3eda3029d34ec9095a27a198ac9785630fe525c0eb6a49f3d575272f8e792ef0"
|
sha256: "3eda3029d34ec9095a27a198ac9785630fe525c0eb6a49f3d575272f8e792ef0"
|
||||||
@@ -698,14 +698,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
mobile_scanner:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: mobile_scanner
|
|
||||||
sha256: c92c26bf2231695b6d3477c8dcf435f51e28f87b1745966b1fe4c47a286171ce
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "7.2.0"
|
|
||||||
native_toolchain_c:
|
native_toolchain_c:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
+1
-1
@@ -43,6 +43,7 @@ dependencies:
|
|||||||
drift: ^2.31.0
|
drift: ^2.31.0
|
||||||
sqlite3_flutter_libs: ^0.5.42
|
sqlite3_flutter_libs: ^0.5.42
|
||||||
crypto: ^3.0.7
|
crypto: ^3.0.7
|
||||||
|
cryptography: ^2.9.0
|
||||||
file_picker: ^10.3.10
|
file_picker: ^10.3.10
|
||||||
saf_util: ^2.0.0
|
saf_util: ^2.0.0
|
||||||
saf_stream: ^2.0.0
|
saf_stream: ^2.0.0
|
||||||
@@ -51,7 +52,6 @@ dependencies:
|
|||||||
in_app_review: ^2.0.11
|
in_app_review: ^2.0.11
|
||||||
dart_discord_presence: ^1.2.0
|
dart_discord_presence: ^1.2.0
|
||||||
flutter_svg: ^2.2.3
|
flutter_svg: ^2.2.3
|
||||||
mobile_scanner: ^7.2.0
|
|
||||||
background_downloader: ^9.5.2
|
background_downloader: ^9.5.2
|
||||||
sentry_flutter:
|
sentry_flutter:
|
||||||
git:
|
git:
|
||||||
|
|||||||
Reference in New Issue
Block a user