From f4cc1a4f1ea504179280d044f695e088e1224ab8 Mon Sep 17 00:00:00 2001
From: MaryamShaghaghi <122574719+MaryamShaghaghi@users.noreply.github.com>
Date: Mon, 27 Nov 2023 14:29:50 +0100
Subject: [PATCH 1/7] Add string resources
Co-Authored-By: Boban Sijuk <49131853+boki91@users.noreply.github.com>
---
.../lib/resource/src/main/res/values-da/strings.xml | 12 ++++++++++++
.../lib/resource/src/main/res/values-de/strings.xml | 12 ++++++++++++
.../lib/resource/src/main/res/values-es/strings.xml | 12 ++++++++++++
.../lib/resource/src/main/res/values-fi/strings.xml | 12 ++++++++++++
.../lib/resource/src/main/res/values-fr/strings.xml | 12 ++++++++++++
.../lib/resource/src/main/res/values-it/strings.xml | 12 ++++++++++++
.../lib/resource/src/main/res/values-ja/strings.xml | 12 ++++++++++++
.../lib/resource/src/main/res/values-ko/strings.xml | 12 ++++++++++++
.../lib/resource/src/main/res/values-my/strings.xml | 12 ++++++++++++
.../lib/resource/src/main/res/values-nb/strings.xml | 12 ++++++++++++
.../lib/resource/src/main/res/values-nl/strings.xml | 12 ++++++++++++
.../lib/resource/src/main/res/values-pl/strings.xml | 12 ++++++++++++
.../lib/resource/src/main/res/values-pt/strings.xml | 12 ++++++++++++
.../lib/resource/src/main/res/values-ru/strings.xml | 12 ++++++++++++
.../lib/resource/src/main/res/values-sv/strings.xml | 12 ++++++++++++
.../lib/resource/src/main/res/values-th/strings.xml | 12 ++++++++++++
.../lib/resource/src/main/res/values-tr/strings.xml | 12 ++++++++++++
.../resource/src/main/res/values-zh-rCN/strings.xml | 12 ++++++++++++
.../resource/src/main/res/values-zh-rTW/strings.xml | 12 ++++++++++++
android/lib/resource/src/main/res/values/strings.xml | 12 ++++++++++++
20 files changed, 240 insertions(+)
diff --git a/android/lib/resource/src/main/res/values-da/strings.xml b/android/lib/resource/src/main/res/values-da/strings.xml
index 160a34ea5f9e..faa43cde8b9b 100644
--- a/android/lib/resource/src/main/res/values-da/strings.xml
+++ b/android/lib/resource/src/main/res/values-da/strings.xml
@@ -12,10 +12,13 @@
Køb enten kredit på vores hjemmeside, eller indløs en kupon.
Accepter og fortsæt
Alle applikationer
+ Alle udbydere
Giver adgang til andre enheder på det samme netværk til deling, udskrivning osv.
Kunne ikke starte tunnelforbindelse. Deaktiver Altid-til VPN for <b>%1$s</b>.
Altid-til VPN tildelt en anden app
+ Enhver
App-version
+ Anvend
Kan ikke godkende konto. Indsend en problemrapport.
Auto-tilslutning
Opret automatisk forbindelse til en server, når appen starter.
@@ -85,6 +88,8 @@
Kunne ikke sende
Hvis du lukker formularen og prøver igen senere, vil de oplysninger, du allerede har indtastet, stadig være her.
Ofte stillede spørgsmål og vejledninger
+ Filter
+ Filtreret:
Viser den aktuelle VPN-tunnelstatus
VPN-tunnelstatus
Gå til login
@@ -119,11 +124,13 @@
Log ud af mindst én ved at fjerne den fra listen nedenfor. Du kan finde det tilsvarende enhedsnavn under enhedens kontoindstillinger.
For mange enheder
Mullvad-kontonummer
+ Kun ejet af Mullvad
Velkommen! Denne enhed hedder nu <b>%1$s</b>. Se info-knappen i Konto for at flere oplysninger.
NY ENHED OPRETTET
Ingen servere matcher dine indstillinger. Prøv at ændre server eller andre indstillinger.
Gyldig WireGuard-nøgle mangler. Administrer nøgler under Avancerede indstillinger.
DU LÆKKER MÅSKE NETVÆRKSTRAFIK
+ Udbydere: %1$d
Tilsløring skjuler WireGuard-trafikken inden i en anden protokol. Det kan bruges til at hjælpe med at omgå censur og andre typer filtrering, hvor en almindelig WireGuard-forbindelse ville blive blokeret.
Til (UDP-over-TCP)
WireGuard-tilsløring
@@ -131,6 +138,8 @@
Til
Ud
Tid udløbet
+ Ejet
+ Ejerskab
Betalt indtil
For at begynde at bruge appen skal du først føje tid til din konto.
Tid blev tilføjet
@@ -138,6 +147,7 @@
Privatliv
Fortrolighedspolitik
For at vi bedre kan hjælpe dig, bedes du vedhæfte din apps logfil til denne meddelelse. Dine data vil forblive sikre og private, da de anonymiseres, før de sendes via en krypteret kanal.
+ Udbydere
OPRETTER KVANTESIKKER FORBINDELSE
Denne funktion gør WireGuard-tunnelen modstandsdygtig over for potentielle angreb fra kvantecomputere.
Det gør den ved at udføre en ekstra nøgleudveksling ved hjælp af en kvantesikker algoritme og blande resultatet med WireGuards almindelige kryptering. Dette ekstra trin bruger cirka 500 kB trafik, hver gang en ny tunnel etableres.
@@ -147,6 +157,8 @@
Indløs
Indløs kupon
Fjern
+ Lejet
+ Kun lejet
Rapporter et problem
Nulstil til standard
Søg efter...
diff --git a/android/lib/resource/src/main/res/values-de/strings.xml b/android/lib/resource/src/main/res/values-de/strings.xml
index fd6b6f8d59e6..f4d33a913d0b 100644
--- a/android/lib/resource/src/main/res/values-de/strings.xml
+++ b/android/lib/resource/src/main/res/values-de/strings.xml
@@ -12,10 +12,13 @@
Kaufen Sie entweder Guthaben über unsere Seite oder lösen Sie einen Gutschein ein.
Akzeptieren und weiter
Alle Anwendungen
+ Alle Anbieter
Ermöglicht den Zugriff auf andere Geräte im selben Netzwerk zum Teilen von Dateien, Drucken etc.
Tunnelverbindung kann nicht gestartet werden. Bitte deaktivieren Sie Always-on VPN für <b>%1$s</b>, bevor Sie Mullvad VPN verwenden.
Always-on VPN ist einer anderen App zugeordnet
+ Beliebige
App-Version
+ Anwenden
Konto konnte nicht authentifiziert werden. Bitte senden Sie einen Problembericht.
Automatische Verbindung
Stellt automatisch eine Verbindung zum Server her, wenn die App startet.
@@ -85,6 +88,8 @@
Fehler beim Senden
Die Informationen, die Sie bereits eingegeben haben, werden immer noch da sein, wenn Sie das Formular schließen und es später erneut versuchen.
Häufig gestellte Fragen & Anleitungen
+ Filter
+ Gefiltert:
Zeigt den aktuellen Status des VPN-Tunnels an
Status des VPN-Tunnels
Zur Anmeldung
@@ -119,11 +124,13 @@
Bitte melden Sie sich von mindestens einem Gerät ab, indem Sie es aus der Liste unten entfernen. Sie finden den entsprechenden Gerätenamen unter den Kontoeinstellungen des Geräts.
Zu viele Geräte
Mullvad-Kontonummer
+ Nur im Besitz von Mullvad
Dieses Gerät heißt jetzt <b>%1$s</b>. Weitere Details finden Sie über die Info-Schaltfläche in Ihrem Konto.
NEUES GERÄT ERSTELLT
Kein Server entspricht Ihren Einstellungen. Versuchen Sie, den Server oder andere Einstellungen zu ändern.
Gültiger WireGuard-Schlüssel fehlt. Sie können Ihre Schlüssel in den erweiterten Einstellungen verwalten.
MÖGLICHERWEISE IST IHR NETZWERKVERKEHR UNSICHER
+ Provider: %1$d
Bei der Verschleierung wird der WireGuard-Datenverkehr in einem anderen Protokoll versteckt. Sie kann dazu verwendet werden, Zensur und andere Arten von Filtern zu umgehen, bei denen eine reine WireGuard-Verbindung blockiert würde.
An (UDP über TCP)
WireGuard-Verschleierung
@@ -131,6 +138,8 @@
Ein
Ausgehend
Zeit abgelaufen
+ In Besitz
+ Eigentümerschaft
Bezahlt bis
Um mit der Nutzung dieser App zu beginnen, müssen Sie erst einmal Zeit zu Ihrem Konto hinzufügen.
Zeit erfolgreich hinzugefügt
@@ -138,6 +147,7 @@
Datenschutz
Datenschutzrichtlinie
Damit wir Ihnen besser helfen können, wird die Protokolldatei Ihrer App an diese Nachricht angehängt. Ihre Daten bleiben sicher und privat, da sie vor dem Senden über einen verschlüsselten Kanal anonymisiert werden.
+ Anbieter
QUANTENSICHERE VERBINDUNG WIRD ERSTELLT
Diese Funktion macht den WireGuard-Tunnel resistent gegen mögliche Angriffe von Quantencomputern.
Dazu wird ein zusätzlicher Schlüsselaustausch mit einem quantensicheren Algorithmus durchgeführt und das Ergebnis mit der regulären Verschlüsselung von WireGuard vermischt. Dieser zusätzliche Schritt verbraucht jedes Mal, wenn ein neuer Tunnel aufgebaut wird, etwa 500 KiB an Datenverkehr.
@@ -147,6 +157,8 @@
Einlösen
Gutschein einlösen
Entfernen
+ Gemietet
+ Nur gemietet
Problem melden
Auf Standard zurücksetzen
Suchen nach …
diff --git a/android/lib/resource/src/main/res/values-es/strings.xml b/android/lib/resource/src/main/res/values-es/strings.xml
index 4eff30768e37..73dca6fe802b 100644
--- a/android/lib/resource/src/main/res/values-es/strings.xml
+++ b/android/lib/resource/src/main/res/values-es/strings.xml
@@ -12,10 +12,13 @@
Compre crédito en nuestro sitio web o canjee un cupón.
Aceptar y continuar
Todas las aplicaciones
+ Todos los proveedores
Permite el acceso a otros dispositivos de la misma red para compartir, imprimir, etc.
No se puede iniciar la conexión de túnel. Deshabilite la VPN siempre activa en <b>%1$s</b> antes de utilizar la VPN de Mullvad.
La VPN siempre activa se ha asignado a otra aplicación
+ Cualquiera
Versión de la aplicación
+ Aplicar
No se puede autenticar la cuenta. Envíe un informe de problemas.
Conexión automática
Al iniciarse la aplicación, se conecta automáticamente a un servidor.
@@ -85,6 +88,8 @@
No se pudo enviar
Si cierra el formulario y vuelve a intentarlo más tarde, la información que haya escrito seguirá estando disponible.
Preguntas frecuentes y guías
+ Filtrar
+ Filtros aplicados:
Muestra el estado actual del túnel VPN
Estado del túnel VPN
Iniciar sesión
@@ -119,11 +124,13 @@
Cierre la sesión como mínimo en un dispositivo (para hacerlo, quítelo de la lista siguiente). Consulte el nombre del dispositivo en la configuración de la cuenta del dispositivo.
Demasiados dispositivos
Número de cuenta de Mullvad
+ Solo propiedad de Mullvad
Hola, este dispositivo se llama ahora <b>%1$s</b>. Para más información, consulte el botón de información en la Cuenta.
NUEVO DISPOSITIVO CREADO
Ningún servidor coincide con su configuración, pruebe con otro servidor u otra configuración.
Falta una clave de WireGuard válida. Para administrar las claves, vaya a Configuración avanzada.
PUEDE QUE SE ESTÉ FILTRANDO EL TRÁFICO DE RED
+ Proveedores: %1$d
La ofuscación oculta el tráfico de WireGuard dentro de otro protocolo. Puede usarse para sortear la censura y otros tipos de filtrado donde podría bloquearse una conexión de WireGuard normal.
Activado (UDP sobre TCP)
Ofuscación de WireGuard
@@ -131,6 +138,8 @@
Activado
Salida
Tiempo agotado
+ Propios
+ Propiedad
Pagado hasta
Para empezar a usar la aplicación, primero necesita agregar tiempo a su cuenta.
Se añadió correctamente el tiempo
@@ -138,6 +147,7 @@
Privacidad
Política de privacidad
Para ayudarle de una forma más eficiente, se adjuntará el archivo de registro de la aplicación a este mensaje. Sus datos permanecerán protegidos y privados, ya que se anonimizan antes de enviarse a través de un canal cifrado.
+ Proveedores
CREANDO CONEXIÓN CON SEGURIDAD CUÁNTICA
Esta característica permite que el túnel de WireGuard resista posibles ataques de ordenadores cuánticos.
Lo hace al realizar un intercambio de claves adicional usando un algoritmo cuántico seguro y combinando el resultado en el cifrado normal de WireGuard. Este paso extra utiliza aproximadamente 500 kiB de tráfico cada vez que se establece un nuevo túnel.
@@ -147,6 +157,8 @@
Canjear
Canjear cupón
Quitar
+ Alquilados
+ Solo alquilados
Informar de un problema
Restablecer a valores predeterminados
Buscar...
diff --git a/android/lib/resource/src/main/res/values-fi/strings.xml b/android/lib/resource/src/main/res/values-fi/strings.xml
index a48af8610ea8..01a56b3e1cd6 100644
--- a/android/lib/resource/src/main/res/values-fi/strings.xml
+++ b/android/lib/resource/src/main/res/values-fi/strings.xml
@@ -12,10 +12,13 @@
Osta käyttöaikaa verkkosivustoltamme tai lunasta kuponki.
Hyväksy ja jatka
Kaikki sovellukset
+ Kaikki palveluntarjoajat
Sallii jakamisen, tulostuksen ym. saman verkon muille laitteille.
Tunneliyhteyden käynnistäminen ei onnistu. Poista aina päällä oleva VPN käytöstä sovellukselle <b>%1$s</b> ennen Mullvad VPN:n käyttämistä.
Aina päällä oleva VPN on määritetty toiselle sovellukselle
+ Mikä tahansa
Sovelluksen versio
+ Ota käyttöön
Tilin todentaminen ei onnistu. Lähetä ongelmaraportti.
Automaattinen yhteys
Muodosta yhteys palvelimeen automaattisesti, kun sovellus avataan.
@@ -85,6 +88,8 @@
Lähetys epäonnistui
Jos poistut lomakkeelta ja yrität myöhemmin uudelleen, jo syöttämäsi tiedot säilyvät lomakkeella.
Usein kysytyt kysymykset ja oppaat
+ Suodatin
+ Suodatettu:
Näyttää VPN-tunnelin nykyisen tilan
VPN-tunnelin tila
Siirry kirjautumiseen
@@ -119,11 +124,13 @@
Kirjaudu ulos vähintään yhdestä luettelon laitteesta poistamalla se. Löydät vastaavan laitteen nimen laitteen tiliasetuksista.
Liikaa laitteita
Mullvad-tilin numero
+ Vain Mullvadin omistamat
Tervetuloa! Tämän laitteen nimi on nyt <b>%1$s</b>. Katso lisätietoja tilin infopainikkeesta.
UUSI LAITE LUOTIIN
Mikään palvelin ei vastaa asetuksiasi. Kokeile vaihtaa palvelinta tai muuttaa muita asetuksia.
Käypä WireGuard-avain puuttuu. Voit hallinnoida avaimia lisäasetuksissa.
VERKKOLIIKENTEESI SAATTAA VUOTAA
+ Palveluntarjoajat: %1$d
Hämäysteknologian käyttäminen piilottaa WireGuard-liikenteen toisen protokollan sisään. Sitä voidaan käyttää kiertämään sensuuria ja muita suodatuksia niissä tapauksissa, kun tavallinen WireGuard-yhteys muutoin estettäisi.
Käytössä (UDP TCP:n kautta)
WireGuard-obfuskointi
@@ -131,6 +138,8 @@
Päällä
Lähtevä
Ei käyttöaikaa
+ Omistettu
+ Omistajuus
Maksu ennen
Voit aloittaa sovelluksen käyttämisen lisäämällä ensin aikaa tilillesi.
Aika lisättiin onnistuneesti
@@ -138,6 +147,7 @@
Tietosuoja
Tietosuojakäytäntö
Jotta voimme olla avuksi parhaamme mukaan, sovelluksesi lokitiedosto liitetään tähän viestiin. Tietosi pysyvät suojattuina ja yksityisinä, ja ne anonymisoidaan salatun kanavan kautta ennen lähetystä.
+ Tarjoajat
LUODAAN KVANTTISUOJATTU YHTEYS
Tämä ominaisuus tekee WireGuard-tunnelista kestävän kvanttitietokoneiden mahdollisia hyökkäyksiä vastaan.
Tunneli torjuu hyökkäykset suorittamalla ylimääräisen avaimenvaihdon käyttämällä ensin kvanttiturvallista algoritmia, jonka tuloksen se sekoittaa WireGuardin tavalliseen salaukseen. Tämä ylimääräinen vaihe käyttää noin 500 kiB liikennettä joka kerta, kun uusi tunneli luodaan.
@@ -147,6 +157,8 @@
Lunasta
Lunasta kuponki
Poista
+ Vuokrattu
+ Vain vuokratut
Raportoi ongelma
Palauta oletusarvo
Hae...
diff --git a/android/lib/resource/src/main/res/values-fr/strings.xml b/android/lib/resource/src/main/res/values-fr/strings.xml
index 0abbdf0037b4..fa614aec4c50 100644
--- a/android/lib/resource/src/main/res/values-fr/strings.xml
+++ b/android/lib/resource/src/main/res/values-fr/strings.xml
@@ -12,10 +12,13 @@
Achetez du crédit sur notre site web ou échangez un bon.
Accepter et continuer
Toutes les applications
+ Tous les fournisseurs
Autorise l\'accès aux autres appareils sur le même réseau pour partager, imprimer, etc.
Impossible de démarrer la connexion au tunnel. Veuillez désactiver « Toujours exiger un VPN « pour <b>%1$s</b> avant d\'utiliser Mullvad VPN.
« Toujours exiger un VPN » est assigné à une autre application
+ N\'importe lequel
Version de l\'application
+ Appliquer
Impossible d\'authentifier le compte. Veuillez envoyer un rapport de problème.
Connexion automatique
Connexion automatique à un serveur dès le démarrage de l\'application.
@@ -85,6 +88,8 @@
Échec de l\'envoi
Si vous quittez le formulaire et réessayez plus tard, les informations que vous avez déjà saisies seront toujours là.
FAQ et guides
+ Filtrer
+ Filtré :
Affiche l\'état actuel du tunnel VPN
État du tunnel VPN
Aller à la connexion
@@ -119,11 +124,13 @@
Merci de vous déconnecter d\'au moins un appareil en le supprimant de la liste ci-dessous. Vous trouverez le nom de l\'appareil correspondant dans les paramètres du compte de l\'appareil.
Trop d\'appareils
Numéro de compte Mullvad
+ Propriété de Mullvad uniquement
Bienvenue, cet appareil s\'appelle désormais <b>%1$s</b>. Pour plus d\'informations, consultez le bouton d\'information sous Compte.
NOUVEL APPAREIL CRÉÉ
Aucun serveur ne correspond à vos paramètres, essayez de modifier les paramètres du serveur ou d\'autres paramètres.
Une clé WireGuard valide manque. Gérez les clés dans les paramètres avancés.
VOUS POURRIEZ AVOIR DES FUITES DE TRAFIC RÉSEAU
+ Fournisseurs : %1$d
La dissimulation cache le trafic WireGuard à l\'intérieur d\'un autre protocole. Elle peut être utilisée pour aider à contourner la censure et d\'autres types de filtrage, où une connexion WireGuard simple serait bloquée.
Activé (UDP sur TCP)
Obfuscation WireGuard
@@ -131,6 +138,8 @@
Activé
Sortante
Plus de temps
+ Possédé
+ Propriété
Payé jusqu\'au
Pour commencer à utiliser l\'application, vous devez d\'abord ajouter du temps à votre compte.
Le temps a bien été ajouté
@@ -138,6 +147,7 @@
Confidentialité
Politique de confidentialité
Pour mieux vous aider, le fichier journal de l\'application est joint à ce message. Vos données restent privées et en sécurité dans la mesure où elles sont rendues anonymes avant d\'être envoyées via un canal chiffré.
+ Fournisseurs
CRÉATION D\'UNE CONNEXION QUANTIQUE SÉCURISÉE
Cette fonctionnalité rend le tunnel WireGuard résistant aux attaques potentielles des ordinateurs quantiques.
Pour ce faire, il effectue un échange de clés supplémentaire à l\'aide d\'un algorithme à sécurité quantique et mélange le résultat au chiffrement habituel de WireGuard. Cette étape supplémentaire utilise environ 500 kiB de trafic chaque fois qu\'un nouveau tunnel est établi.
@@ -147,6 +157,8 @@
Échanger
Échanger un bon
Supprimer
+ Loué
+ Loués uniquement
Signaler un problème
Réinitialiser à la valeur par défaut
Rechercher...
diff --git a/android/lib/resource/src/main/res/values-it/strings.xml b/android/lib/resource/src/main/res/values-it/strings.xml
index 8ae92f4880ee..4df0a0df823e 100644
--- a/android/lib/resource/src/main/res/values-it/strings.xml
+++ b/android/lib/resource/src/main/res/values-it/strings.xml
@@ -12,10 +12,13 @@
Acquista credito sul nostro sito web o riscatta un voucher.
Accetta e continua
Tutte le applicazioni
+ Tutti i fornitori
Consenti l\'accesso ad altri dispositivi sulla stessa rete per condividere, stampare e altro.
Impossibile avviare la connessione tunnel. Disabilita VPN sempre attiva per <b>%1$s</b> prima di utilizzare Mullvad VPN.
VPN sempre attiva assegnata a un\'altra app
+ Qualsiasi
Versione app
+ Applica
Impossibile autenticare l\'account. Invia una segnalazione del problema.
Connessione automatica
Connettiti automaticamente al server all\'avvio dell\'app.
@@ -85,6 +88,8 @@
Impossibile inviare
Se esci dal modulo e riprovi più tardi, le informazioni già inserite saranno ancora qui.
FAQ e guide
+ Filtra
+ Filtrato:
Mostra lo stato attuale del tunnel VPN
Stato del tunnel VPN
Vai al login
@@ -119,11 +124,13 @@
Disconnettiti da almeno un dispositivo rimuovendolo dall\'elenco seguente. Puoi trovare il nome del dispositivo corrispondente nelle impostazioni dell\'account del dispositivo.
Troppi dispositivi
Numero di account Mullvad
+ Solo di proprietà di Mullvad
Benvenuto, questo dispositivo ora si chiama <b>%1$s</b>. Per maggiori dettagli, premi il pulsante delle informazioni in Account.
NUOVO DISPOSITIVO CREATO
Nessun server corrispondente alle tue impostazioni, prova a cambiare server o impostazioni.
Manca una chiave WireGuard valida. Gestisci le chiavi da Impostazioni avanzate.
POSSIBILI PERDITE NEL TRAFFICO DI RETE
+ Fornitori: %1$d
L\'offuscamento nasconde il traffico WireGuard all\'interno di un altro protocollo. Può essere utilizzato per aggirare la censura e altri tipi di filtraggio, in cui una semplice connessione WireGuard verrebbe bloccata.
On (UDP-over-TCP)
Offuscamento WireGuard
@@ -131,6 +138,8 @@
On
Invio
Scaduto
+ Di proprietà
+ Proprietà
Pagato fino al
Per iniziare a utilizzare l\'app, devi prima aggiungere tempo al tuo account.
L\'ora è stata aggiunta correttamente
@@ -138,6 +147,7 @@
Privacy
Informativa sulla privacy
Per aiutarti in modo più efficace, il file di registro della tua app sarà allegato a questo messaggio. I tuoi dati rimarranno protetti e privati, e saranno anonimizzati prima di essere inviati tramite un canale crittografato.
+ Fornitori
CREAZIONE CONNESSIONE QUANTISTICA PROTETTA
Questa funzionalità rende il tunnel WireGuard resistente ai potenziali attacchi dei computer quantistici.
L\'operazione viene effettuata eseguendo uno scambio di chiavi aggiuntivo con un algoritmo di sicurezza quantistica e mescolando il risultato nella normale crittografia di WireGuard. Questo passaggio aggiuntivo utilizza circa 500 kiB di traffico ogni volta che viene stabilito un nuovo tunnel.
@@ -147,6 +157,8 @@
Riscatta
Riscatta voucher
Rimuovi
+ Noleggiato
+ Solo noleggiati
Segnala un problema
Ripristina predefiniti
Cerca...
diff --git a/android/lib/resource/src/main/res/values-ja/strings.xml b/android/lib/resource/src/main/res/values-ja/strings.xml
index a05cdc3db722..0f123d4cb313 100644
--- a/android/lib/resource/src/main/res/values-ja/strings.xml
+++ b/android/lib/resource/src/main/res/values-ja/strings.xml
@@ -12,10 +12,13 @@
当社ウェブサイトでクレジットを購入するか、バウチャーを使用してください。
同意して続行
すべてのアプリケーション
+ すべてのプロバイダ
共有や印刷などのため、同一ネットワーク上の他のデバイスへのアクセスを許可します。
トンネル接続を開始できません。Mullvad VPNを使用する前に<b>%1$s</b>のAlways-on VPNを無効にしてください。
Always-on VPNは他のアプリに割り当てられています
+ すべて
アプリのバージョン
+ 適用
アカウントを認証できません。問題の報告を送信してください。
自動接続
アプリ起動時に自動的にサーバーに接続します。
@@ -85,6 +88,8 @@
送信に失敗しました
フォームを終了して後で再試行しても、入力済みの情報は引き続きここに表示されます。
よくある質問とガイド
+ 絞り込み
+ 絞り込み結果:
現在のVPNトンネルのステータスを表示します
VPNトンネルのステータス
ログインに進む
@@ -119,11 +124,13 @@
以下のリストから少なくとも1つを削除してログアウトしてください。対応するデバイス名はデバイスのアカウント設定で確認できます。
デバイスが多すぎます
Mullvadアカウント番号
+ Mullvad 所有サーバーのみ
ようこそ。このデバイスの名前は<b>%1$s</b>です。詳細はアカウントの情報ボタンで確認してください。
新しいデバイスが作成されました
設定に一致するサーバーはありません。サーバーまたは他の設定を変更してみてください。
有効なWireGuard鍵が見つかりません。詳細設定で鍵を管理してください。
ネットワーク通信が漏洩している可能性があります
+ プロバイダ数: %1$d
難読化は、WireGuardトラフィックを別のプロトコル内に隠します。プレーンなWireGuard接続がブロックされる検閲やその他のフィルタリングを回避するために使用できます。
オン (UDP-over-TCP)
WireGuardの難読化
@@ -131,6 +138,8 @@
オン
外側
時間切れ
+ 自社サーバー
+ 所有権
次の日時まで支払い済み
アプリを使い始めるには、まずはアカウントに時間を追加する必要があります。
時間を正常に追加しました
@@ -138,6 +147,7 @@
プライバシー
プライバシーポリシー
さらに効率よく問題解決できるよう、お使いのアプリのログファイルがこのメッセージに添付されます。個人データは匿名化された後に暗号化されたチャネルで送信されるため、その安全は確保され、公開されることはありません。
+ プロバイダ
量子セキュア保護接続を確立中
この機能は、WireGuardトンネルに量子コンピューターからの潜在的な攻撃に対する耐性を与えます。
耐量子アルゴリズムで追加の鍵の交換を実行し、結果をWireGuardの通常の暗号化に混合させることで行われます。この追加ステップでは、新しいトンネルが確立されるたびに約500kiBのトラフィックが使用されます。
@@ -147,6 +157,8 @@
使用する
バウチャーを使用する
削除
+ レンタルサーバー
+ レンタルサーバーのみ
問題を報告する
デフォルトにリセット
検索...
diff --git a/android/lib/resource/src/main/res/values-ko/strings.xml b/android/lib/resource/src/main/res/values-ko/strings.xml
index b3a098c919b2..e2ac7167911f 100644
--- a/android/lib/resource/src/main/res/values-ko/strings.xml
+++ b/android/lib/resource/src/main/res/values-ko/strings.xml
@@ -12,10 +12,13 @@
웹 사이트에서 크레딧을 구매하거나 바우처를 사용하세요.
동의하고 계속하기
모든 애플리케이션
+ 모든 제공업체
공유, 인쇄 등을 위해 동일한 네트워크의 다른 장치에 액세스할 수 있습니다.
터널 연결을 시작할 수 없습니다. Mullvad VPN을 사용하기 전에 <b>%1$s</b>에 대한 상시 접속 VPN을 비활성화하세요.
상시 접속 VPN이 다른 앱에 할당됨
+ 모두
앱 버전
+ 적용
계정을 인증할 수 없습니다. 문제 보고서를 보내주세요.
자동 연결
앱이 시작되면 자동으로 서버에 연결합니다.
@@ -85,6 +88,8 @@
전송하지 못함
양식을 종료한 후 나중에 다시 시도해도 이미 입력한 정보는 그대로 유지됩니다.
FAQ 및 가이드
+ 필터
+ 필터링됨:
현재 VPN 터널 상태 표시
VPN 터널 상태
로그인하기
@@ -119,11 +124,13 @@
하나 이상의 항목을 아래 목록에서 제거하여 로그아웃하세요. 장치의 계정 설정에서 해당 장치 이름을 찾을 수 있습니다.
장치가 너무 많음
Mullvad 계정 번호
+ Mullvad 소유만
환영합니다! 이제 이 장치의 이름은 <b>%1$s</b>입니다. 자세한 내용을 보려면 계정의 정보 버튼을 누르세요.
새 장치가 생성됨
설정과 일치하는 서버가 없습니다. 서버 또는 기타 설정을 변경해 보세요.
유효한 WireGuard 키가 없습니다. 고급 설정에서 키를 관리하세요.
네트워크 트래픽이 유출될 수 있습니다.
+ 제공업체: %1$d
난독 처리는 다른 프로토콜 내에서 WireGuard 트래픽을 숨깁니다. 일반 WireGuard 연결이 차단되는 상황에서 검열 및 기타 유형의 필터링을 우회하는 데 사용할 수 있습니다.
켜기(UDP-over-TCP)
WireGuard 난독화
@@ -131,6 +138,8 @@
켜기
아웃
시간 초과
+ 소유
+ 소유권
유효 기간
앱 사용을 시작하려면, 먼저 계정에 시간을 추가해야 합니다.
시간이 성공적으로 추가되었습니다.
@@ -138,6 +147,7 @@
개인 정보 보호
개인정보 보호정책
보다 효과적인 문제 해결을 위해 앱의 로그 파일이 이 메시지에 첨부됩니다. 사용자 데이터는 암호화된 채널을 통해 전송되기 전에 익명 처리되므로 안전하고 비공개로 유지됩니다.
+ 제공업체
양자 보안 연결 생성 중
이 기능은 WireGuard 터널이 양자 컴퓨터의 잠재적인 공격에 맞서도록 합니다.
이를 위해 양자 안전 알고리즘을 사용하여 추가 키 교환을 수행하고 결과를 WireGuard의 일반 암호화에 혼합하는 방법이 이용됩니다. 이 추가 단계는 새 터널이 설정될 때마다 약 500kiB의 트래픽을 사용합니다.
@@ -147,6 +157,8 @@
사용
바우처 사용
제거
+ 대여
+ 대여만
문제 신고
기본값으로 재설정
검색...
diff --git a/android/lib/resource/src/main/res/values-my/strings.xml b/android/lib/resource/src/main/res/values-my/strings.xml
index bfd0e3e2bdb1..2f0cc573633b 100644
--- a/android/lib/resource/src/main/res/values-my/strings.xml
+++ b/android/lib/resource/src/main/res/values-my/strings.xml
@@ -12,10 +12,13 @@
ကျွန်ုပ်တို့၏ ဝက်ဘ်ဆိုက်တွင် ခရက်ဒစ် ဝယ်ယူပါ သို့မဟုတ် ဘောက်ချာဖြင့် လဲယူပါ။
သဘောတူပြီး ဆက်လုပ်ရန်
အပလီကေးရှင်း အားလုံး
+ ပံ့ပိုးသူအားလုံး
ဝေမျှရန်၊ ပရင့်ထုတ်ရန်စသည်တို့အတွက် တူညီသည့် ကွန်ရက်ရှိ အခြားစက်များကို ရယူသုံးစွဲခွင့်ပြုပေးပါသည်။
Tunnel ချိတ်ဆက်မှုကို စတင်၍ မရနိုင်ပါ။ Mullvad VPN ကို မသုံးမီ <b>%1$s</b> အတွက် VPN အမြဲဖွင့်ထားမှုကို ပိတ်ပေးပါ။
အမြဲဖွင့် VPN ကို အခြားအက်ပ်တစ်ခုသို့ သတ်မှတ်ထားပါသည်
+ တစ်ခုခု
အက်ပ်ဗားရှင်း
+ သုံးရန်
အကောင့်ကို စစ်မှန်ကြောင်း အတည်ပြု၍ မရနိုင်ပါ။ ပြဿနာ ရီပို့တ်တစ်ခု ပေးပို့ပေးပါ။
အော်တို ချိတ်ဆက်မှု
အက်ပ် စတင်ဆောင်ရွက်ချိန်တွင် ဆာဗာနှင့် အော်တို ချိတ်ဆက်သွားပါမည်။
@@ -85,6 +88,8 @@
ပို့ရန် မအောင်မြင်ခဲ့ပါ
ဖောင်မှ ထွက်ပြီး နောက်မှ ထပ်ကြိုးစားကြည့်ပါ၊ သင်ဖြည့်ထားသည့် အချက်အလက်များသည် ဤတွင် ဆက်ရှိနေပါမည်။
မေးလေ့ရှိသည့် မေးခွန်းများနှင့် လမ်းညွှန်များ
+ စစ်ထုတ်မှု
+ စစ်ထုတ်ထားသော-
လက်ရှိ VPN Tunnel အခြေအနေကို ပြသပေးပါသည်
VPN Tunnel အခြေအနေ
ဝင်ရောက်ရန် သွားပါ
@@ -119,11 +124,13 @@
အောက်ပါစာရင်းမှ အနည်းဆုံး တစ်ခုကို ဖယ်ရှားခြင်းဖြင့် ၎င်းမှ ထွက်ပါ။ စက်၏ အကောင့်ဆက်တင်အောက်တွင် သက်ဆိုင်သော စက်အမည်ကို သင် ရှာနိုင်သည်။
စက်များလွန်းနေသည်
Mullvad အကောင့်နံပါတ်
+ Mullvad ပိုင်ဆိုင်သည်များသာ
ကြိုဆိုပါသည်၊ ယခုမှစ၍ ဤစက်ကို <b>%1$s</b> ဟု ခေါ်ဆိုပါမည်။ နောက်ထပ်အသေးစိတ်တို့အတွက် အကောင့်တွင် အချက်အလက် ခလုတ်ကို နှိပ်၍ ကြည့်နိုင်သည်။
စက်အသစ် ဖန်တီးထားသည်
သင့်ဆက်တင်နှင့် ကိုက်ညီသော ဆာဗာများ မရှိပါ၊ ဆာဗာ သို့မဟုတ် အခြားဆက်တင်တို့ကို ပြောင်းလဲရန် ကြိုးစားကြည့်ပါ။
အကျုံးဝင်သည့် WireGuard ကီး မရှိပါ။ အဆင့်မြင့်ဆက်တင် အောက်တွင် ကီးများကို စီမံခန့်ခွဲပါ။
ကွန်ရက် ကူးလူးမှု ပေါက်ကြားနေနိုင်ပါသည်
+ ပံ့ပိုးသူများ- %1$d
Obfuscation သည် အခြားပရိုတိုကောလ်အတွင်းရှိ WireGuard ကူးလူးမှုကို ဝှက်ထားပေးပါသည်။ သာမန် WireGuard ချိတ်ဆက်မှုကို ပိတ်ဆို့မည့် အခြားသော စစ်ထုတ်မှု အမျိုးအစားများနှင့် ဆင်ဆာဖြတ်တောက်ခြင်းကို ရှောင်လွှဲနိုင်စေရာတွင် ကူညီနိုင်စေရန် ဤသည်ကို သုံးနိုင်ပါသည်။
ဖွင့် (UDP-over-TCP)
WireGuard Obfuscation\\n
@@ -131,6 +138,8 @@
ဖွင့်
အထွက်
အချိန်စေ့သွားပါပြီ
+ အပိုင်
+ ပိုင်ဆိုင်မှု
ဖော်ပြပါအထိ ပေးချေထားပြီး
အက်ပ်ကို စသုံးရန်အတွက် ဦးစွာ သင့်အကောင့်တွင် အချိန်ပေါင်းထည့်ပေးရန် လိုအပ်ပါသည်။
အချိန်ကို အောင်မြင်စွာ ပေါင်းထည့်ပြီးပြီ
@@ -138,6 +147,7 @@
ကိုယ်ရေးအချက်အလက် လုံခြုံရေး
ကိုယ်ပိုင်အချက်အလက် မူဝါဒ
သင့်အား ပိုမိုထိရောက်စွာ ကူညီနိုင်ရန် သင့်အက်ပ်၏ မှတ်တမ်းဖိုင်ကို ဤမက်ဆေ့ချ်နှင့်အတူ တွဲပေးသွားပါမည်။ ကုဒ်ပြောင်းဝှက်ထားသည့် ချန်နယ်မှတစ်ဆင့် မပေးပို့မီ သင့်ဒေတာများကို အမည်မဖော်ဘဲ ထားမည်ဖြစ်သောကြောင့် လျှို့ဝှက်လုံခြုံလျက် ရှိနေပါမည်။
+ ပံ့ပိုးသူများ
QUANTUM လုံခြုံသည့် ချိတ်ဆက်မှုကို ဖန်တီးခြင်း
ဤလုပ်ဆောင်ချက်သည် Quantum ကွန်ပျူတာများမှ ဖြစ်လာနိုင်ခြေရှိသော တိုက်ခိုက်မှုများကို ခုခံနိုင်သည့် WireGuard Tunnel ကို ပြုလုပ်သည်။
Quantum Safe အယ်လဂိုရီသမ်တစ်ခုကို သုံး၍ ထပ်ဆောင်း ကီးဖလှယ်မှုတစ်ခုကို ဆောင်ရွက်ပြီး WireGuard ၏ ပုံမှန် ကုဒ်ပြောင်းဝှက်မှုအတွင်း ရလဒ်ကို ရောနှောခြင်းအားဖြင့် ဤသည်ကို လုပ်ဆောင်ပါသည်။ ဤထပ်ဆောင်းအဆင့်သည် Tunnel အသစ်တစ်ခု တည်ဆောက်တိုင်း ဒေတာ 500 kiB ခန့်ကို သုံးပါသည်။
@@ -147,6 +157,8 @@
လဲယူရန်
ဘောက်ချာဖြင့် လဲယူရန်
ဖယ်ရှားရန်
+ အငှား
+ အငှားသီးသန့်
ပြဿနာ ရီပို့တ်လုပ်ရန်
ပုံသေသို့ ပြန်လည်သတ်မှတ်ရန်
ရှာရန်...
diff --git a/android/lib/resource/src/main/res/values-nb/strings.xml b/android/lib/resource/src/main/res/values-nb/strings.xml
index d9fa3d5e8557..4ef8f14ba2f9 100644
--- a/android/lib/resource/src/main/res/values-nb/strings.xml
+++ b/android/lib/resource/src/main/res/values-nb/strings.xml
@@ -12,10 +12,13 @@
Du kan enten kjøpe kreditt på nettsiden vår eller løse inn en kupong.
Godta og fortsett
Alle applikasjoner
+ Alle leverandører
Gir tilgang til andre enheter på samme nettverk for deling, utskrift osv.
Kunne ikke starte tunneltilkobling. Deaktiver VPN som alltid er på, for <b>%1$s</b> før du bruker Mullvad VPN.
VPN som alltid er på, er tilordnet en annen app
+ Hvilken som helst
Appversjon
+ Angi
Kunne ikke autentisere konto. Send inn en problemrapport.
Automatisk tilkobling
Kobler automatisk til en server når appen starter.
@@ -85,6 +88,8 @@
Kunne ikke sende
Hvis du avslutter skjemaet og prøver igjen senere, vil informasjonen du allerede har lagt inn fortsatt være der.
Ofte stilte spørsmål og veiledninger
+ Filter
+ Filtrert:
Viser gjeldende VPN-tunnelstatus
VPN-tunnelstatus
Gå til pålogging
@@ -119,11 +124,13 @@
Logg ut av minst én ved å fjerne den fra listen nedenfor. Du finner det tilsvarende enhetsnavnet under enhetens kontoinnstillinger.
For mange enheter
Mullvad-kontonummer
+ Kun eid av Mullvad
Velkommen. Denne enheten har fått navnet <b>%1$s</b>. For å finne ut mer kan du bruke informasjonsknappen under Konto.
NY ENHET OPPRETTET
Ingen servere passer til innstillingene dine. Prøv å endre server eller andre innstillinger.
Det mangler en gyldig WireGuard-nøkkel. Du kan behandle nøklene under avanserte innstillinger.
DET KAN VÆRE EN NETTVERKSLEKKASJE HOS DEG
+ Leverandører: %1$d
Tilsløring skjuler WireGuard-trafikken i en annen protokoll. Man kan på den måten omgå sensur og andre typer filter i tilfeller der en vanlig WireGuard-tilkobling ville blitt blokkert.
På (UDP-over-TCP)
Tilsløring av WireGuard
@@ -131,6 +138,8 @@
På
Utgående
Tiden har utløpt
+ Eid
+ Eierskap
Betalt fram til
For å starte bruken av appen, må du først legge til tid til kontoen.
Tid ble lagt til
@@ -138,6 +147,7 @@
Personvern
Retningslinjer for personvern
For å kunne gi deg god nok hjelp vil loggfilen til appen ligge som vedlegg til meldingen. All data forblir beskyttet og privat gjennom anonymisering før det sendes gjennom en kryptert kanal.
+ Leverandører
OPPRETTER KVANTESIKKER TILKOBLING
Denne funksjonen gjør WireGuard-tunnelen motstandsdyktig mot potensielle angrep fra kvantemaskiner.
Det gjøres ved at å utføre en ekstra nøkkelutveksling med en kvantesikker algoritme og kombinere resultatet med WireGuard sin vanlige kryptering. Dette ekstratrinnet bruker omtrent 500 kiB trafikk hver gang det opprettes en ny tunnel.
@@ -147,6 +157,8 @@
Løs inn
Løs inn kupong
Fjern
+ Leid
+ Kun leid
Rapporter et problem
Tilbakestill til standard
Søk etter ...
diff --git a/android/lib/resource/src/main/res/values-nl/strings.xml b/android/lib/resource/src/main/res/values-nl/strings.xml
index 07de8454437d..8797e6fddd85 100644
--- a/android/lib/resource/src/main/res/values-nl/strings.xml
+++ b/android/lib/resource/src/main/res/values-nl/strings.xml
@@ -12,10 +12,13 @@
Koop krediet op onze website of wissel een voucher in.
Akkoord en doorgaan
Alle toepassingen
+ Alle aanbieders
Biedt toegang tot andere apparaten op hetzelfde netwerk voor delen, afdrukken en dergelijke
Kan de tunnelverbinding niet starten. Schakel Altijd-aan VPN uit voor <b>%1$s</b> voordat u Mullvad VPN gebruikt.
Altijd-aan VPN toegewezen aan andere app
+ Elke
Appversie
+ Toevoegen
Kan account niet verifiëren. Stuur een probleemrapport.
Automatisch verbinden
Automatisch verbinden met een server wanneer de app wordt gestart.
@@ -85,6 +88,8 @@
Verzenden mislukt
Als u het formulier verlaat en het later opnieuw probeert, is de reeds ingevoerde informatie er nog.
Veelgestelde vragen en gidsen
+ Filter
+ Gefilterd:
Toont de huidige status van de VPN-tunnel
Status VPN-tunnel
Ga naar aanmelden
@@ -119,11 +124,13 @@
Meld u bij minstens één apparaat af door het te verwijderen uit de onderstaande lijst. U kunt de bijbehorende apparaatnaam vinden in de accountinstellingen van het apparaat.
Te veel apparaten
Mullvad-accountnummer
+ Alleen in eigendom van Multivad
Welkom, dit apparaat heet nu <b>%1$s</b>. Zie voor meer informatie de infoknop in Account.
NIEUW APPARAAT GEMAAKT
Er zijn geen servers die overeenkomen met uw instellingen. Probeer een andere server of andere instellingen.
Geldige WireGuard-sleutel ontbreekt. Beheer sleutels onder Geavanceerde instellingen.
U LEKT MOGELIJK NETWERKVERKEER
+ Providers: %1$d
Obfuscatie verbergt het WireGuard-verkeer in een ander protocol. Het kan worden gebruikt om censuur en andere soorten filtering te omzeilen, waar een gewone WireGuard-verbinding zou worden geblokkeerd.
Aan (UDP-over-TCP)
WireGuard-obfuscatie
@@ -131,6 +138,8 @@
Aan
Uit
Geen tijd meer
+ In eigendom
+ Eigendom
Betaald tot
Om de app te gebruiken, moet u eerst tijd toevoegen aan uw account.
Tijd is toegevoegd
@@ -138,6 +147,7 @@
Privacy
Privacybeleid
Het logboekbestand van uw app wordt aan dit bericht gekoppeld zodat we u beter kunnen helpen. Uw gegevens blijven veilig en privé, omdat ze worden geanonimiseerd voordat ze over een versleuteld kanaal worden verzonden.
+ Aanbieders
BEVEILIGDE QUANTUMVERBINDING AANMAKEN
Deze eigenschap maakt de WireGuard-tunnel bestand tegen mogelijke aanvallen met kwantumcomputers.
Het doet dit door een extra sleuteluitwisseling uit te voeren met een kwantumveilig algoritme en het resultaat te mengen met de reguliere versleuteling van WireGuard. Deze extra stap gebruikt ongeveer 500 kiB aan verkeer elke keer dat een nieuwe tunnel wordt opgezet.
@@ -147,6 +157,8 @@
Inwisselen
Voucher inwisselen
Verwijderen
+ Gehuurd
+ Alleen gehuurde
Een probleem rapporteren
Standaardwaarde herstellen
Zoeken naar...
diff --git a/android/lib/resource/src/main/res/values-pl/strings.xml b/android/lib/resource/src/main/res/values-pl/strings.xml
index fdbc8b58bbaa..e50c23e8ff2b 100644
--- a/android/lib/resource/src/main/res/values-pl/strings.xml
+++ b/android/lib/resource/src/main/res/values-pl/strings.xml
@@ -12,10 +12,13 @@
Doładuj w naszej witrynie internetowej lub zrealizuj kupon.
Zaakceptuj i kontynuuj
Wszystkie aplikacje
+ Wszyscy dostawcy
Umożliwia dostęp do innych urządzeń w tej samej sieci w celu udostępniania, drukowania itd.
Nie można uruchomić połączenia tunelowego. Przed rozpoczęciem użytkowania usługi Mullvad VPN wyłącz opcję „Zawsze włączony VPN” w <b>%1$s</b>.
Opcja „Zawsze włączony VPN” przypisana jest do innej aplikacji
+ Dowolny
Wersja aplikacji
+ Zastosuj
Nie można uwierzytelnić konta. Wyślij zgłoszenie problemu.
Automatyczne łączenie
Automatycznie łącz z serwerem po uruchomieniu aplikacji.
@@ -85,6 +88,8 @@
Błąd wysyłania
Jeśli wyjdziesz z formularza i spróbujesz ponownie później, zastaniesz już wprowadzone dane.
Często zadawane pytania i poradniki
+ Filtruj
+ Odfiltrowane:
Pokazuje bieżący status tunelu VPN
Status tunelu VPN
Przejdź do logowania
@@ -119,11 +124,13 @@
Wyloguj się z co najmniej jednego urządzenia, usuwając je z poniższej listy. Odpowiednią nazwę urządzenia można znaleźć w ustawieniach konta urządzenia.
Zbyt wiele urządzeń
Numer konta Mullvad
+ Wyłącznie firmy Mullvad
Witaj, to urządzenie nazywa się teraz <b>%1$s</b>. Więcej szczegółów znajdziesz, korzystając z przycisku Informacje na koncie.
UTWORZONO NOWE URZĄDZENIE
Żaden serwer nie odpowiada ustawieniom. Spróbuj zmienić serwer lub inne ustawienia.
Brak prawidłowego klucza WireGuard. Zarządzaj kluczami w Ustawieniach zaawansowanych.
TWÓJ RUCH SIECIOWY MOŻE WYCIEKAĆ
+ Dostawcy: %1$d
Zaciemnianie ukrywa ruch WireGuard w innym protokole. Można go użyć do obchodzenia cenzury i innych typów filtrowania, w których zwykłe połączenie WireGuard byłoby blokowane.
Wł. (UDP-przez-TCP)
Zaciemnianie WireGuard
@@ -131,6 +138,8 @@
Wł.
Wyjście
Koniec czasu
+ Posiadane
+ Własność
Płatne do
Aby rozpocząć korzystanie z aplikacji, musisz najpierw dodać czas do swojego konta.
Dodano czas
@@ -138,6 +147,7 @@
Prywatność
Polityka prywatności
Aby można było pomóc Ci skuteczniej, do tej wiadomości dołączony zostanie plik dziennika aplikacji. Twoje dane pozostaną bezpieczne i prywatne, ponieważ przed wysłaniem zaszyfrowanym kanałem zostają one zanonimizowane.
+ Dostawcy
TWORZENIE KWANTOWO BEZPIECZNEGO POŁĄCZENIA
Ta funkcja sprawia, że tunel WireGuard jest odporny na potencjalne ataki ze strony komputerów kwantowych.
Jest to wykonywane poprzez dodatkową wymianę kluczy przy użyciu algorytmu odpornego na ataki z użyciem komputerów kwantowych i zmieszanie wyniku ze zwykłym szyfrowaniem WireGuard. Ten dodatkowy krok zużywa około 500 kB ruchu za każdym razem, gdy ustanawiany jest nowy tunel.
@@ -147,6 +157,8 @@
Zrealizuj
Zrealizuj kupon
Usuń
+ Wynajmowane
+ Wyłącznie wynajmowane
Zgłoś problem
Przywróć domyślne
Wyszukaj...
diff --git a/android/lib/resource/src/main/res/values-pt/strings.xml b/android/lib/resource/src/main/res/values-pt/strings.xml
index 702161aeff98..586f8f1f0f4c 100644
--- a/android/lib/resource/src/main/res/values-pt/strings.xml
+++ b/android/lib/resource/src/main/res/values-pt/strings.xml
@@ -12,10 +12,13 @@
Compre crédito no nosso sítio da web ou reclame um voucher.
Concordar e continuar
Todas as aplicações
+ Todos os fornecedores
Permite o acesso a outros dispositivos na mesma rede para partilha, impressão, etc.
Não foi possível iniciar a ligação de túnel. Desative a VPN sempre ligada para <b>%1$s</b> antes de utilizar a Mullvad VPN.
VPN sempre ligada atribuída a outra app
+ Qualquer
Versão da app
+ Aplicar
Não foi possível autenticar a conta. Envie um relatório do problema.
Ligação automática
Liga-se automaticamente a um servidor quando a app inicia.
@@ -85,6 +88,8 @@
Erro no envio
Se sair do formulário e tentar novamente mais tarde, a informação que já tiver introduzido continuará aqui.
Perguntas frequentes e guias
+ Filtrar
+ Filtrado:
Indica o estado atual do túnel VPN
Estado do túnel VPN
Ir para a ligação
@@ -119,11 +124,13 @@
Desligue-se de pelo menos um dos dispositivos removendo-o da lista abaixo. Pode encontrar o nome do dispositivo correspondente nas definições de Conta do dispositivo.
Demasiados dispositivos
Número de conta Mullvad
+ Apenas propriedade de Mullvad
Bem-vindo, este dispositivo é agora chamado <b>%1$s</b>. Para mais detalhes consulte o botão de informação na Conta.
NOVO DISPOSITIVO CRIADO
Nenhum servidor corresponde às suas definições. Tente alterar o servidor ou outras definições.
Chave WireGuard válida em falta. Faça a gestão das chaves em Definições Avançadas.
PODERÁ ESTAR A PERDER TRÁFEGO DE REDE
+ Fornecedores: %1$d
A ofuscação oculta o tráfego do WireGuard dentro de outro protocolo. Pode ser utilizado para ajudar a contornar a censura e outros tipos de filtragem, onde uma simples ligação WireGuard seria bloqueada.
Ligado (UDP sobre TCP)
Ofuscação WireGuard
@@ -131,6 +138,8 @@
Ligado
Saída
Sem tempo
+ Propriedade de
+ Propriedade
Pago até
Para começar a utilizar a aplicação, primeiro tem de adicionar tempo à sua conta.
Tempo adicionado com sucesso
@@ -138,6 +147,7 @@
Privacidade
Política de privacidade
Para ajudarmos de forma mais eficaz, o ficheiro de registo da sua aplicação será anexado a esta mensagem. Os seus dados permanecerão seguros e privados, pois são tornados anónimos antes de serem enviados através de um canal encriptado.
+ Fornecedores
A CRIAR LIGAÇÃO COM SEGURANÇA QUÂNTICA
Esta funcionalidade torna o túnel do WireGuard resistente a potenciais ataques de computadores quânticos.
Fá-lo ao realizar uma troca de chaves adicional utilizando um algoritmo de segurança quântica e misturando o resultado na encriptação regular do WireGuard. Este passo adicional utiliza aproximadamente 500 kiB de tráfego sempre que um novo túnel é estabelecido.
@@ -147,6 +157,8 @@
Reclamar
Reclamar voucher
Remover
+ Alugado
+ Apenas alugado
Reportar um problema
Repor para as predefinições
Pesquisar por...
diff --git a/android/lib/resource/src/main/res/values-ru/strings.xml b/android/lib/resource/src/main/res/values-ru/strings.xml
index c2f122a86265..035680b0943d 100644
--- a/android/lib/resource/src/main/res/values-ru/strings.xml
+++ b/android/lib/resource/src/main/res/values-ru/strings.xml
@@ -12,10 +12,13 @@
Пополните баланс у нас на сайте или погасите ваучер.
Согласиться и продолжить
Все приложения
+ Все провайдеры
Разрешить доступ к другим устройствам в той же сети для организации общего доступа, печати и т. д.
Не удалось запустить туннельное соединение. Перед использованием Mullvad VPN отключите опцию «VPN всегда вкл.» для <b>%1$s</b>.
Опция «VPN всегда вкл.» назначена другому приложению
+ Все
Версия приложения
+ Применить
Не удается произвести аутентификацию учетной записи. Отправьте сообщение о проблеме.
Автоподключение
Автоматически подключаться к серверу при запуске приложения.
@@ -85,6 +88,8 @@
Ошибка отправки
Если вы выйдете из формы и повторите попытку позже, информация, которую вы уже ввели, сохранится.
Ответы на вопросы и руководства
+ Фильтр
+ Фильтр:
Показывает текущее состояние VPN-туннеля
Состояние туннеля VPN
Войти
@@ -119,11 +124,13 @@
Выйдите из учетной записи хотя бы на одном из устройств, удалив его из списка ниже. Имя устройства указано в настройках учетной записи.
Слишком много устройств
Номер учетной записи Mullvad
+ Только принадлежащие Mullvad
Добро пожаловать, теперь это устройство называется <b>%1$s</b>. Для получения более подробной нажмите на кнопку «Информация» в учетной записи.
СОЗДАНО НОВОЕ УСТРОЙСТВО
Нет серверов, соответствующих вашим настройкам. Попробуйте изменить сервер или задайте другие настройки.
Не найден действительный ключ WireGuard. Управлять ключами можно в дополнительных настройках.
ВОЗМОЖНА УТЕЧКА СЕТЕВОГО ТРАФИКА
+ провайдеры: %1$d
Обфускация скрывает трафик WireGuard внутри другого протокола. Это может использоваться для обхода цензуры и других видов фильтрации, когда обычное соединение WireGuard было бы заблокировано.
Вкл. (UDP через TCP)
Обфускация WireGuard
@@ -131,6 +138,8 @@
Включен
Выход
Закончилось время
+ Во владении
+ Собственность
Оплачено до
Чтобы пользоваться приложением, нужно добавить время на учетную запись.
Время добавлено
@@ -138,6 +147,7 @@
Конфиденциальность
Политика конфиденциальности
Чтобы помощь была эффективнее, к этому сообщению будет прикреплен файл журнала из приложения. Ваши данные останутся защищенными и конфиденциальными: они обезличиваются и отправляются по зашифрованному каналу.
+ Провайдеры
СОЗДАНИЕ ПОДКЛЮЧЕНИЯ С ЗАЩИТОЙ ОТ КВАНТОВЫХ АТАК
Эта функция делает туннель WireGuard устойчивым к потенциальным атакам с использованием квантовых компьютеров.
Для этого функция выполняет дополнительный обмен ключами с использованием квантово-устойчивого алгоритма и добавляет результат к обычному шифрованию WireGuard. Эта дополнительная мера использует примерно 500 КиБ трафика при каждом создании нового туннеля.
@@ -147,6 +157,8 @@
Погасить
Погасить ваучер
Удалить
+ Арендованные
+ Только арендованные
Сообщение о проблеме
Восстановить значение по умолчанию
Поиск...
diff --git a/android/lib/resource/src/main/res/values-sv/strings.xml b/android/lib/resource/src/main/res/values-sv/strings.xml
index c453cb2fa9fa..5c542ae382a9 100644
--- a/android/lib/resource/src/main/res/values-sv/strings.xml
+++ b/android/lib/resource/src/main/res/values-sv/strings.xml
@@ -12,10 +12,13 @@
Du kan antingen köpa kredit på vår webbplats eller lösa in en kupong.
Godkänn och fortsätt
Alla applikationer
+ Alla leverantörer
Tillåter åtkomst till andra enheter på samma nätverk för delning, utskrift osv.
Det går inte att starta tunnelanslutning. Aktivera VPN som alltid är på för <b>%1$s</b> innan du använder Mullvad VPN.
VPN som alltid är på har tilldelats till annan app
+ Valfri
Appversion
+ Använd
Det går inte att autentisera kontot. Skicka en problemrapport.
Anslut automatiskt
Anslut automatiskt till en server när appen startas.
@@ -85,6 +88,8 @@
Det gick inte att skicka
Om du lämnar formuläret och försöker igen senare kommer informationen du anger att finnas kvar.
Vanliga frågor och guider
+ Filtrera
+ Filtrerat:
Visar nuvarande status för VPN-tunnel
VPN-tunnelstatus
Gå till inloggning
@@ -119,11 +124,13 @@
Logga ut på minst en enhet genom att ta bort den från listan nedan. Du hittar motsvarande enhetsnamn i enhetens kontoinställningar.
För många enheter
Mullvad-kontonummer
+ Endast Mullvad-ägd
Välkommen! Den här enheten heter nu <b>%1$s</b>. Använd informationsknappen i Konto för mer information.
NY ENHET HAR SKAPATS
Inga servrar matchar dina inställningar. Försök att byta server eller ändra inställningarna.
Giltig WireGuard-nyckel saknas. Hantera nycklar i Avancerade inställningar.
DU KANSKE HAR LÄCKAGE I NÄTVERKSTRAFIKEN
+ Leverantörer: %1$d
Obfuskering döljer WireGuard-trafik inne i ett annat protokoll. Det kan användas för att kringgå censur och andra filtertyper där en vanlig WireGuard-anslutning skulle blockeras.
På (UDP över TCP)
WireGuard-obfuskering
@@ -131,6 +138,8 @@
På
Ut
Ingen tid kvar
+ Ägd
+ Ägarskap
Betalat till
Om du vill börja använda appen måste du först lägga till tid i ditt konto.
Tid har lagts till
@@ -138,6 +147,7 @@
Sekretess
Sekretesspolicy
För att hjälpa dig mer effektivt kommer appens loggfil att bifogas i detta meddelande. Dina uppgifter förblir säkra och privata, eftersom de anonymiseras innan de skickas över en krypterad kanal.
+ Leverantörer
SKAPAR KVANTSÄKER ANSLUTNING
Den här funktionen gör WireGuard-tunneln resistent mot potentiella attacker från kvantdatorer.
Den gör det genom att göra ett extra nyckelutbyte med en kvantsäker algoritm och kombinera resultatet med WireGuards vanliga kryptering. Det här extra steget använder ungefär 500 KiB i trafik varje gång en ny tunnel upprättas.
@@ -147,6 +157,8 @@
Lös in
Lös in kupong
Ta bort
+ Hyrd
+ Endast hyrd
Rapportera ett problem
Återställ till standard
Sök efter …
diff --git a/android/lib/resource/src/main/res/values-th/strings.xml b/android/lib/resource/src/main/res/values-th/strings.xml
index cce7148a7f83..281adadf577f 100644
--- a/android/lib/resource/src/main/res/values-th/strings.xml
+++ b/android/lib/resource/src/main/res/values-th/strings.xml
@@ -12,10 +12,13 @@
ซื้อเครดิตบนเว็บไซต์ของเรา หรือแลกรับบัตรกำนัล
ยอมรับและดำเนินการต่อ
แอปพลิเคชันทั้งหมด
+ ผู้ให้บริการทั้งหมด
อนุญาตให้เข้าถึงอุปกรณ์อื่นๆ บนเครือข่ายเดียวกัน เพื่อแชร์ พิมพ์ ฯลฯ
ไม่สามารถเริ่มการเชื่อมต่ออุโมงค์ได้ โปรดปิดใช้งาน Always-on VPN เป็นเวลา <b>%1$s</b> ก่อนที่จะใช้งาน Mullvad VPN
Always-on VPN ได้รับการมอบหมายไปยังแอปอื่นแล้ว
+ อะไรก็ได้
เวอร์ชันแอป
+ ใช้
ไม่สามารถตรวจสอบความถูกต้องของบัญชีได้ โปรดส่งรายงานปัญหา
เชื่อมต่ออัตโนมัติ
เชื่อมต่อเซิร์ฟเวอร์โดยอัตโนมัติทันทีที่เปิดแอป
@@ -85,6 +88,8 @@
ไม่สามารถส่งได้
หากคุณออกจากแบบฟอร์มแล้วเข้ามาใหม่ในภายหลัง ข้อมูลที่คุณกรอกไว้จะยังคงอยู่ที่นี่ดังเดิม
คำถามที่พบบ่อยและคำแนะนำ
+ ตัวกรอง
+ กรอง:
แสดงสถานะอุโมงค์ VPN ในปัจจุบัน
สถานะอุโมงค์ VPN
ไปเข้าสู่ระบบ
@@ -119,11 +124,13 @@
โปรดลงชื่อออกจากระบบบนอุปกรณ์อย่างน้อยหนึ่งเครื่อง เพื่อนำอุปกรณ์ออกจากรายการด้านล่าง คุณสามารถดูชื่ออุปกรณ์ที่เกี่ยวข้องได้ ภายใต้การตั้งค่าบัญชีของอุปกรณ์
มีอุปกรณ์มากเกินไป
หมายเลขบัญชี Mullvad
+ ของ Mullvad เท่านั้น
ยินดีต้อนรับ ขณะนี้อุปกรณ์นี้จะมีชื่อว่า <b>%1$s</b> สำหรับข้อมูลเพิ่มเติม โปรดกดปุ่มข้อมูลในบัญชี
สร้างอุปกรณ์ใหม่แล้ว
ไม่มีเซิร์ฟเวอร์ที่ตรงกับการตั้งค่าของคุณ โปรดลองเปลี่ยนเซิร์ฟเวอร์ หรือการตั้งค่าอื่นๆ
คีย์ WireGuard ที่ใช้ได้ขาดหายไป จัดการคีย์ภายใต้การตั้งค่าขั้นสูง
คุณอาจมีการรับส่งข้อมูลทางเครือข่ายที่รั่วไหลอยู่
+ ผู้ให้บริการ: %1$d
การทำให้ข้อมูลยุ่งเหยิง จะซ่อนการรับส่งข้อมูล WireGuard ภายในอีกโพรโทคอลหนึ่ง ซึ่งใช้เพื่อช่วยหลบเลี่ยงการเซ็นเซอร์ และการกรองประเภทอื่นๆ ที่การเชื่อมต่อ WireGuard แบบธรรมดาจะถูกบล็อกได้
เปิด (UDP-ผ่าน-TCP)
การทำให้ข้อมูลยุ่งเหยิงของ WireGuard
@@ -131,6 +138,8 @@
เปิด
ออก
หมดเวลา
+ เป็นเจ้าของ
+ ความเป็นเจ้าของ
ชำระเงินแล้วจนถึง
คุณจำเป็นต้องเพิ่มเวลาไปยังบัญชีของคุณก่อน เพื่อที่จะเริ่มใช้งานแอป
เพิ่มเวลาสำเร็จแล้ว
@@ -138,6 +147,7 @@
ความเป็นส่วนตัว
นโยบายความเป็นส่วนตัว
ไฟล์บันทึกล็อกของแอปของคุณจะถูกแนบไปกับข้อความนี้ เพื่อที่เราจะช่วยเหลือคุณได้อย่างมีประสิทธิภาพมากขึ้น ข้อมูลของคุณจะยังคงมีความปลอดภัยและเป็นส่วนตัว เนื่องจากจะไม่มีการระบุตัวตนก่อนส่งข้อมูลผ่านช่องทางที่มีการเข้ารหัส
+ ผู้ให้บริการ
กำลังสร้างการเชื่อมต่อควอนตัมที่ปลอดภัย
ฟีเจอร์นี้จะช่วยให้ช่องทาง WireGuard สามารถสกัดกั้นการโจมตีที่อาจมาจากคอมพิวเตอร์ควอนตัมได้
ระบบจะดำเนินการสิ่งนี้ผ่านการแลกเปลี่ยนคีย์เพิ่มเติม โดยการใช้อัลกอริทึมแบบควอนตัมที่ปลอดภัย และผสมผลลัพธ์เข้ากับการเข้ารหัสตามปกติของ WireGuard และขั้นตอนพิเศษนี้ใช้การรับส่งข้อมูลประมาณ 500 kiB ในทุกครั้งที่สร้างช่องทางใหม่
@@ -147,6 +157,8 @@
แลกรับ
แลกบัตรกำนัล
ลบ
+ เช่า
+ เช่าเท่านั้น
รายงานปัญหา
รีเซ็ตเป็นค่าเริ่มต้น
ค้นหา…
diff --git a/android/lib/resource/src/main/res/values-tr/strings.xml b/android/lib/resource/src/main/res/values-tr/strings.xml
index 92a18749f5c7..ff14abcaadd5 100644
--- a/android/lib/resource/src/main/res/values-tr/strings.xml
+++ b/android/lib/resource/src/main/res/values-tr/strings.xml
@@ -12,10 +12,13 @@
Web sitemizden kredi satın alın veya kupon kullanın.
Kabul et ve devam et
Tüm uygulamalar
+ Tüm hizmet sağlayıcılar
Paylaşım, yazdırma gibi özellikler için aynı ağdaki diğer cihazlara erişim izni verir.
Tünel bağlantısı başlatılamıyor. Mullvad VPN\'i kullanmadan önce lütfen Her zaman açık VPN\'i <b>%1$s</b> için devre dışı bırakın.
Her zaman açık VPN başka bir uygulamaya atandı
+ Tümü
Uygulama sürümü
+ Uygula
Hesap doğrulanamıyor. Lütfen bir hata raporu gönderin.
Otomatik Bağlan
Uygulama başladığında bir sunucuya otomatik olarak bağlan.
@@ -85,6 +88,8 @@
Gönderme başarısız
Formdan çıkıp daha sonra tekrar denerseniz, girmiş olduğunuz bilgiler hala burada olacaktır.
SSS ve Kılavuzlar
+ Filtrele
+ Filtrelendi:
Mevcut VPN tünelinin durumunu gösterir
VPN tüneli durumu
Giriş sayfasına git
@@ -119,11 +124,13 @@
Lütfen aşağıdaki listeden en az bir cihazı kaldırarak çıkış yapın. İlgili cihaz adını cihazın Hesap ayarları altında bulabilirsiniz.
Cihaz sayısı çok fazla
Mullvad hesap numarası
+ Sadece Mullvad\'a ait olanlar
Hoş geldiniz, bu cihaz artık <b>%1$s</b> olarak adlandırılıyor. Daha fazla ayrıntı için Hesaptaki bilgi düğmesine bakın.
YENİ CİHAZ OLUŞTURULDU
Ayarlarınızla eşleşen sunucu yok. Sunucuyu veya diğer ayarları değiştirmeyi deneyin.
Geçerli WireGuard anahtarı eksik. Gelişmiş ayarlardan anahtarları yönetin.
AĞ TRAFİĞİNİZDE SIZINTI OLABİLİR
+ Hizmet sağlayıcılar: %1$d
Gizleme, WireGuard trafiğini başka bir protokolün içinde gizler. Normal bir WireGuard bağlantısının engelleneceği sansürü ve diğer filtreleme türlerini aşmaya yardımcı olmak için kullanılabilir.
Açık (TCP üzerinden UDP)
WireGuard gizlemesi
@@ -131,6 +138,8 @@
Açık
Çıkış
Süre doldu
+ Tarafımıza ait olanlar
+ Sahiplik durumu
Şu tarihe kadar ödendi:
Uygulamayı kullanmaya başlamak için önce hesabınıza süre eklemeniz gerekir.
Süre başarıyla eklendi
@@ -138,6 +147,7 @@
Gizlilik
Gizlilik politikası
Size daha etkili bir şekilde yardımcı olmak için uygulamanızın günlük dosyası bu mesaja eklenecektir. Verileriniz şifrelenmiş bir kanal üzerinden gönderilmeden önce anonimleştirildiği için güvenli ve gizli kalacaktır.
+ Hizmet sağlayıcılar
KUANTUM GÜVENLİ BAĞLANTISI OLUŞTURULUYOR
Bu özellik, WireGuard tünelini kuantum bilgisayarlardan gelebilecek potansiyel saldırılara karşı dayanıklı hale getirir.
Bu işlemi, bir kuantum güvenlik algoritmasıyla ekstra bir anahtar değişimi gerçekleştirdikten sonra sonucu WireGuard\'ın normal şifrelemesiyle karıştırarak yapar. Bu ekstra adım, her yeni tünel kurulduğunda yaklaşık 500 kiB trafik kullanır.
@@ -147,6 +157,8 @@
Kullan
Kuponu kullan
Kaldır
+ Kiralananlar
+ Sadece kiralananlar
Bir sorun bildir
Varsayılana sıfırla
Ara...
diff --git a/android/lib/resource/src/main/res/values-zh-rCN/strings.xml b/android/lib/resource/src/main/res/values-zh-rCN/strings.xml
index 26c61fa644b1..830ca1aa776c 100644
--- a/android/lib/resource/src/main/res/values-zh-rCN/strings.xml
+++ b/android/lib/resource/src/main/res/values-zh-rCN/strings.xml
@@ -12,10 +12,13 @@
在我们的网站上购买额度或兑换优惠券。
同意并继续
所有应用程序
+ 所有提供商
允许访问同一网络上的其他设备,以进行共享、打印等
无法启动隧道连接。在使用 Mullvad VPN 之前,请为 <b>%1$s</b> 禁用“始终启用 VPN”。
“始终启用 VPN”已分配给其他应用
+ 任何
应用版本
+ 应用
无法验证帐户。请发送问题报告。
自动连接
在应用启动时自动连接到服务器。
@@ -85,6 +88,8 @@
无法发送
如果您退出窗体并稍后再试,您输入的信息仍会在这里。
常见问题解答与指南
+ 筛选
+ 已筛选:
显示当前的 VPN 隧道状态
VPN 隧道状态
前往登录
@@ -119,11 +124,13 @@
请通过从以下列表中移除的方式退出至少一个帐户。您可以在设备的帐户设置下找到相应设备名称。
设备过多
Mullvad 帐号
+ 仅 Mullvad 自有
欢迎,此设备现在名为 <b>%1$s</b>。有关详情,请点击“帐户”中的信息按钮。
已创建新设备
没有与您的设置匹配的服务器,请尝试更改服务器或其他设置。
缺少有效的 WireGuard 密钥。在“高级”设置下管理密钥。
您的网络流量可能在泄露
+ 提供商:%1$d
混淆将 WireGuard 流量隐藏在另一个协议中。它可用于帮助规避审查和其他类型的过滤,在这些过滤中,普通的 WireGuard 连接将被阻止。
开 (UDP-over-TCP)
WireGuard 混淆
@@ -131,6 +138,8 @@
开
外部
已没有时间
+ 自有
+ 所有权
到期时间
要开始使用本应用,您首先需要向帐户中充入时间。
时间已成功添加
@@ -138,6 +147,7 @@
隐私
隐私政策
为了更有效地帮助您,您应用的日志文件将被附加到此消息。您的数据将保持安全和私密,因为所有数据在发送之前都将通过加密通道进行匿名处理。
+ 提供商
正在创建量子安全连接
借助此功能,WireGuard 隧道能够抵抗可能通过量子计算机发起的攻击。
实现方法是使用量子安全算法执行额外的密钥交换,并将结果混合到 WireGuard 的常规加密中。每次建立新隧道时,这一额外步骤都会使用约 500 kiB 的流量。
@@ -147,6 +157,8 @@
兑换
兑换优惠券
移除
+ 租用
+ 仅租用
报告问题
重置为默认值
搜索…
diff --git a/android/lib/resource/src/main/res/values-zh-rTW/strings.xml b/android/lib/resource/src/main/res/values-zh-rTW/strings.xml
index a3eee83ddea8..2c5c779384d0 100644
--- a/android/lib/resource/src/main/res/values-zh-rTW/strings.xml
+++ b/android/lib/resource/src/main/res/values-zh-rTW/strings.xml
@@ -12,10 +12,13 @@
在我們網站上購買點數或兌換憑證。
同意並繼續
所有應用程式
+ 所有供應商
允許存取同一網路上的其他裝置,以進行分享、列印等。
無法啟動通道連線。在使用 Mullvad VPN 之前,請先為 <b>%1$s</b> 停用「始終啟用 VPN」。
「始終啟用 VPN」已指派給其他應用程式
+ 任何
應用程式版本
+ 套用
無法驗證帳戶。請傳送問題回報。
自動連線
啟動應用程式時,自動連線伺服器。
@@ -85,6 +88,8 @@
無法傳送
如果您關閉表格後再重新嘗試,所輸入的資訊仍會出現在這裡。
常見問題集與指南
+ 篩選
+ 已篩選:
顯示目前的 VPN 通道狀態
VPN 通道狀態
前往登入
@@ -119,11 +124,13 @@
請從底下清單至少移除一個裝置來將其登出。您可以在裝置的「帳戶」設定下找到相應裝置名稱。
裝置過多
Mullvad 帳號
+ 僅 Mullvad 自有
歡迎,此裝置現在稱為 <b>%1$s</b>。如需詳細資訊,請點按「帳戶」中的資訊按鈕。
已建立新裝置
沒有與您的設定相符的伺服器,請嘗試變更伺服器或其他設定。
缺少有效的 WireGuard 金鑰。在「進階」設定下管理金鑰。
您的網路流量可能正在洩露
+ 供應商:%1$d
藉由混淆,WireGuard 的流量能隱藏在另一個通訊協定中。這有助於規避審查或其他類型的篩選。在這類篩選中,普通 WireGuard 連線將遭到封鎖。
開 (UDP-over-TCP)
WireGuard 混淆
@@ -131,6 +138,8 @@
開啟
出境
逾時
+ 自有
+ 所有權狀態
支付至
需先在帳戶中加時,才能開始使用本應用程式。
已成功新增時間
@@ -138,6 +147,7 @@
隱私權
隱私權政策
為了更有效協助您,會將應用程式的日誌檔將附加到此郵件。您的資料會保持安全和私密性,因為這些資料會先經過匿名處理,再透過加密通道傳送。
+ 供應商
正在建立量子安全連線
借助此功能,WireGuard 通道便能夠抵抗可能從量子電腦發起的攻擊。
實現方法是使用量子安全演算法執行額外的金鑰交換,並將結果混合至 WireGuard 的常規加密。每次建立新通道時,這一額外步驟都會使用約 500 kiB 流量。
@@ -147,6 +157,8 @@
兌換
兌換憑證
移除
+ 租用
+ 僅租用
回報問題
重設為預設值
搜尋…
diff --git a/android/lib/resource/src/main/res/values/strings.xml b/android/lib/resource/src/main/res/values/strings.xml
index b10dd68ce372..5b33b45f575e 100644
--- a/android/lib/resource/src/main/res/values/strings.xml
+++ b/android/lib/resource/src/main/res/values/strings.xml
@@ -51,6 +51,17 @@
Privacy policy
Account number
Device name
+ Apply
+ Ownership
+ Providers
+ Any
+ Owned
+ Rented
+ Providers: %d
+ Filtered:
+ Mullvad owned only
+ All providers
+ Rented only
This is the name assigned to the device. Each device logged in on a Mullvad account gets a unique name that helps you identify it when you manage your devices in the app or on the website.
You can have up to 5 devices logged in on one Mullvad account.
If you log out, the device and the device name is removed. When you log back in again, the device will get a new name.
@@ -120,6 +131,7 @@
You are running an unsupported app version.
Account credit expires soon
Select location
+ Filter
While connected, your real location is masked with a private and secure location in the selected region.
Choose the apps you want to exclude from the VPN tunnel.
Enable
From 369d82354fe0b494fa50b1b6798dae5e07f217c3 Mon Sep 17 00:00:00 2001
From: MaryamShaghaghi <122574719+MaryamShaghaghi@users.noreply.github.com>
Date: Mon, 27 Nov 2023 14:30:52 +0100
Subject: [PATCH 2/7] Add filter chip component
Co-Authored-By: Boban Sijuk <49131853+boki91@users.noreply.github.com>
---
.../compose/component/FilterChip.kt | 65 +++++++++++++++++++
.../lib/theme/dimensions/Dimensions.kt | 6 ++
.../mullvadvpn/lib/theme/shape/Shape.kt | 13 ++++
3 files changed, 84 insertions(+)
create mode 100644 android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/FilterChip.kt
create mode 100644 android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/shape/Shape.kt
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/FilterChip.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/FilterChip.kt
new file mode 100644
index 000000000000..0443a7267ef0
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/FilterChip.kt
@@ -0,0 +1,65 @@
+package net.mullvad.mullvadvpn.compose.component
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FilterChip
+import androidx.compose.material3.FilterChipDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.lib.theme.AppTheme
+import net.mullvad.mullvadvpn.lib.theme.Dimens
+import net.mullvad.mullvadvpn.lib.theme.color.MullvadBlue
+import net.mullvad.mullvadvpn.lib.theme.shape.chipShape
+
+@Preview
+@Composable
+private fun PreviewMullvadFilterChip() {
+ AppTheme {
+ MullvadFilterChip(
+ text = stringResource(id = R.string.number_of_providers),
+ onRemoveClick = {}
+ )
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun MullvadFilterChip(text: String, onRemoveClick: () -> Unit) {
+ FilterChip(
+ modifier = Modifier.padding(vertical = Dimens.chipVerticalPadding),
+ shape = MaterialTheme.shapes.chipShape,
+ colors = FilterChipDefaults.filterChipColors(containerColor = MullvadBlue),
+ border =
+ FilterChipDefaults.filterChipBorder(
+ borderColor = Color.Transparent,
+ disabledBorderColor = Color.Transparent
+ ),
+ selected = false,
+ onClick = {},
+ label = {
+ Text(
+ text = text,
+ color = MaterialTheme.colorScheme.onPrimary,
+ style = MaterialTheme.typography.labelMedium
+ )
+ Spacer(modifier = Modifier.size(ButtonDefaults.IconSpacing))
+ Image(
+ painter = painterResource(id = R.drawable.icon_close),
+ contentDescription = null,
+ modifier = Modifier.size(Dimens.smallIconSize).clickable { onRemoveClick() }
+ )
+ }
+ )
+}
diff --git a/android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/dimensions/Dimensions.kt b/android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/dimensions/Dimensions.kt
index f0eea30fa6a1..702260741695 100644
--- a/android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/dimensions/Dimensions.kt
+++ b/android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/dimensions/Dimensions.kt
@@ -18,7 +18,10 @@ data class Dimensions(
val cellStartPadding: Dp = 22.dp,
val cellTopPadding: Dp = 6.dp,
val cellVerticalSpacing: Dp = 14.dp,
+ val checkBoxSize: Dp = 24.dp,
val chevronMargin: Dp = 4.dp,
+ val chipSpace: Dp = 8.dp,
+ val chipVerticalPadding: Dp = 4.dp,
val circularProgressBarLargeSize: Dp = 44.dp,
val circularProgressBarLargeStrokeWidth: Dp = 6.dp,
val circularProgressBarMediumSize: Dp = 32.dp,
@@ -32,6 +35,7 @@ data class Dimensions(
val dialogIconHeight: Dp = 44.dp,
val dialogIconSize: Dp = 48.dp,
val expandableCellChevronSize: Dp = 30.dp,
+ val filterTittlePadding: Dp = 4.dp,
val iconFailSuccessTopMargin: Dp = 30.dp,
val iconHeight: Dp = 44.dp,
val indentedCellStartPadding: Dp = 38.dp,
@@ -58,9 +62,11 @@ data class Dimensions(
val searchFieldHeight: Dp = 42.dp,
val searchFieldHorizontalPadding: Dp = 22.dp,
val searchIconSize: Dp = 24.dp,
+ val selectFilterTitlePadding: Dp = 12.dp,
val selectLocationTitlePadding: Dp = 12.dp,
val selectableCellTextMargin: Dp = 12.dp,
val sideMargin: Dp = 22.dp,
+ val smallIconSize: Dp = 16.dp,
val smallPadding: Dp = 8.dp,
val spacingAboveButton: Dp = 22.dp,
val successIconVerticalPadding: Dp = 26.dp,
diff --git a/android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/shape/Shape.kt b/android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/shape/Shape.kt
new file mode 100644
index 000000000000..501cb72946e8
--- /dev/null
+++ b/android/lib/theme/src/main/kotlin/net/mullvad/mullvadvpn/lib/theme/shape/Shape.kt
@@ -0,0 +1,13 @@
+package net.mullvad.mullvadvpn.lib.theme.shape
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Shapes
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.dp
+
+val Shapes.chipShape: Shape
+ @Composable
+ get() {
+ return RoundedCornerShape(8.dp)
+ }
From feb76dafe6cea14b8f121616c778385743e36d8b Mon Sep 17 00:00:00 2001
From: MaryamShaghaghi <122574719+MaryamShaghaghi@users.noreply.github.com>
Date: Mon, 27 Nov 2023 14:31:59 +0100
Subject: [PATCH 3/7] Add new filter cell and checkbox cell
Co-Authored-By: Boban Sijuk <49131853+boki91@users.noreply.github.com>
---
.../mullvadvpn/compose/cell/CheckboxCell.kt | 83 +++++++++++++++++++
.../mullvadvpn/compose/cell/FilterCell.kt | 82 ++++++++++++++++++
2 files changed, 165 insertions(+)
create mode 100644 android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/CheckboxCell.kt
create mode 100644 android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterCell.kt
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/CheckboxCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/CheckboxCell.kt
new file mode 100644
index 000000000000..5c6157e032e6
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/CheckboxCell.kt
@@ -0,0 +1,83 @@
+package net.mullvad.mullvadvpn.compose.cell
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Checkbox
+import androidx.compose.material3.CheckboxDefaults
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import net.mullvad.mullvadvpn.lib.theme.AppTheme
+import net.mullvad.mullvadvpn.lib.theme.Dimens
+import net.mullvad.mullvadvpn.lib.theme.color.MullvadGreen
+
+@Preview
+@Composable
+private fun PreviewCheckboxCell() {
+ AppTheme { CheckboxCell(providerName = "", checked = false, onCheckedChange = {}) }
+}
+
+@Composable
+internal fun CheckboxCell(
+ modifier: Modifier = Modifier,
+ providerName: String,
+ checked: Boolean,
+ onCheckedChange: (Boolean) -> Unit,
+ background: Color = MaterialTheme.colorScheme.secondaryContainer,
+ startPadding: Dp = Dimens.cellStartPadding,
+ endPadding: Dp = Dimens.cellEndPadding,
+ minHeight: Dp = Dimens.cellHeight
+) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier =
+ modifier
+ .clickable { onCheckedChange(!checked) }
+ .defaultMinSize(minHeight = minHeight)
+ .fillMaxWidth()
+ .background(background)
+ .padding(start = startPadding, end = endPadding)
+ ) {
+ Box(
+ modifier =
+ Modifier.size(Dimens.checkBoxSize)
+ .background(Color.White, MaterialTheme.shapes.small)
+ ) {
+ Checkbox(
+ modifier = Modifier.fillMaxSize(),
+ checked = checked,
+ onCheckedChange = onCheckedChange,
+ colors =
+ CheckboxDefaults.colors(
+ checkedColor = Color.Transparent,
+ uncheckedColor = Color.Transparent,
+ checkmarkColor = MullvadGreen
+ ),
+ )
+ }
+
+ Spacer(modifier = Modifier.size(Dimens.mediumPadding))
+
+ Text(
+ text = providerName,
+ style = MaterialTheme.typography.labelLarge,
+ color = MaterialTheme.colorScheme.onSecondary,
+ modifier =
+ Modifier.weight(1f)
+ .padding(top = Dimens.mediumPadding, bottom = Dimens.mediumPadding)
+ )
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterCell.kt
new file mode 100644
index 000000000000..6566a9f30e5a
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterCell.kt
@@ -0,0 +1,82 @@
+package net.mullvad.mullvadvpn.compose.cell
+
+import androidx.compose.foundation.horizontalScroll
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.compose.component.MullvadFilterChip
+import net.mullvad.mullvadvpn.lib.theme.AppTheme
+import net.mullvad.mullvadvpn.lib.theme.Dimens
+import net.mullvad.mullvadvpn.model.Ownership
+
+@Preview
+@Composable
+private fun PreviewFilterCell() {
+ AppTheme {
+ FilterCell(
+ ownershipFilter = Ownership.MullvadOwned,
+ selectedProviderFilter = 3,
+ removeOwnershipFilter = {},
+ removeProviderFilter = {}
+ )
+ }
+}
+
+@Composable
+fun FilterCell(
+ ownershipFilter: Ownership?,
+ selectedProviderFilter: Int?,
+ removeOwnershipFilter: () -> Unit,
+ removeProviderFilter: () -> Unit
+) {
+ val scrollState = rememberScrollState()
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier =
+ Modifier.horizontalScroll(scrollState)
+ .padding(
+ horizontal = Dimens.searchFieldHorizontalPadding,
+ vertical = Dimens.selectLocationTitlePadding
+ )
+ .fillMaxWidth(),
+ ) {
+ Text(
+ modifier = Modifier.padding(end = Dimens.filterTittlePadding),
+ text = stringResource(id = R.string.filtered),
+ color = MaterialTheme.colorScheme.onPrimary,
+ style = MaterialTheme.typography.labelMedium
+ )
+
+ if (selectedProviderFilter != null) {
+ MullvadFilterChip(
+ text = stringResource(id = R.string.number_of_providers, selectedProviderFilter),
+ onRemoveClick = removeProviderFilter
+ )
+ Spacer(modifier = Modifier.size(Dimens.chipSpace))
+ }
+
+ if (ownershipFilter != null) {
+ MullvadFilterChip(
+ text = stringResource(ownershipFilter.stringResources()),
+ onRemoveClick = removeOwnershipFilter
+ )
+ }
+ }
+}
+
+private fun Ownership.stringResources(): Int =
+ when (this) {
+ Ownership.MullvadOwned -> R.string.owned
+ Ownership.Rented -> R.string.rented
+ }
From 63025aaa3152c4bddea0b960bf663c9a5797d59d Mon Sep 17 00:00:00 2001
From: MaryamShaghaghi <122574719+MaryamShaghaghi@users.noreply.github.com>
Date: Mon, 27 Nov 2023 14:35:11 +0100
Subject: [PATCH 4/7] Add filter screen
Co-Authored-By: Boban Sijuk <49131853+boki91@users.noreply.github.com>
---
.../mullvadvpn/compose/button/ApplyButton.kt | 38 ++++
.../mullvadvpn/compose/screen/FilterScreen.kt | 193 ++++++++++++++++++
.../state/FilterConstrainExtensions.kt | 34 +++
.../compose/state/RelayFilterState.kt | 35 ++++
.../mullvadvpn/ui/fragment/FilterFragment.kt | 42 ++++
.../mullvadvpn/viewmodel/FilterViewModel.kt | 107 ++++++++++
6 files changed, 449 insertions(+)
create mode 100644 android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/ApplyButton.kt
create mode 100644 android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreen.kt
create mode 100644 android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/FilterConstrainExtensions.kt
create mode 100644 android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/RelayFilterState.kt
create mode 100644 android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/FilterFragment.kt
create mode 100644 android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModel.kt
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/ApplyButton.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/ApplyButton.kt
new file mode 100644
index 000000000000..bea306454838
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/button/ApplyButton.kt
@@ -0,0 +1,38 @@
+package net.mullvad.mullvadvpn.compose.button
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.compose.component.SpacedColumn
+import net.mullvad.mullvadvpn.lib.theme.AppTheme
+
+@Preview
+@Composable
+private fun PreviewApplyButton() {
+ AppTheme {
+ SpacedColumn {
+ ApplyButton(onClick = {}, isEnabled = true)
+ ApplyButton(onClick = {}, isEnabled = false)
+ }
+ }
+}
+
+@Composable
+fun ApplyButton(
+ modifier: Modifier = Modifier,
+ background: Color = MaterialTheme.colorScheme.background,
+ onClick: () -> Unit,
+ isEnabled: Boolean
+) {
+ VariantButton(
+ background = background,
+ text = stringResource(id = R.string.apply),
+ onClick = onClick,
+ modifier = modifier,
+ isEnabled = isEnabled,
+ )
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreen.kt
new file mode 100644
index 000000000000..844360c16c93
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreen.kt
@@ -0,0 +1,193 @@
+package net.mullvad.mullvadvpn.compose.screen
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.Divider
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.compose.button.ApplyButton
+import net.mullvad.mullvadvpn.compose.cell.CheckboxCell
+import net.mullvad.mullvadvpn.compose.cell.ExpandableComposeCell
+import net.mullvad.mullvadvpn.compose.cell.SelectableCell
+import net.mullvad.mullvadvpn.compose.state.RelayFilterState
+import net.mullvad.mullvadvpn.lib.theme.AppTheme
+import net.mullvad.mullvadvpn.lib.theme.Dimens
+import net.mullvad.mullvadvpn.model.Ownership
+import net.mullvad.mullvadvpn.relaylist.Provider
+
+@Preview
+@Composable
+private fun PreviewFilterScreen() {
+ val state =
+ RelayFilterState(
+ selectedOwnership = null,
+ allProviders = listOf(),
+ selectedProviders = listOf(),
+ )
+ AppTheme {
+ FilterScreen(
+ uiState = state,
+ onSelectedOwnership = {},
+ onSelectedProviders = { _, _ -> },
+ onAllProviderCheckChange = {},
+ uiCloseAction = MutableSharedFlow()
+ )
+ }
+}
+
+@Composable
+fun FilterScreen(
+ uiState: RelayFilterState,
+ onBackClick: () -> Unit = {},
+ uiCloseAction: SharedFlow,
+ onApplyClick: () -> Unit = {},
+ onSelectedOwnership: (ownership: Ownership?) -> Unit = {},
+ onAllProviderCheckChange: (isChecked: Boolean) -> Unit = {},
+ onSelectedProviders: (checked: Boolean, provider: Provider) -> Unit
+) {
+ var providerExpanded by rememberSaveable { mutableStateOf(false) }
+ var ownershipExpanded by rememberSaveable { mutableStateOf(false) }
+
+ val backgroundColor = MaterialTheme.colorScheme.background
+
+ LaunchedEffect(Unit) { uiCloseAction.collect { onBackClick() } }
+ Scaffold(
+ topBar = {
+ Row(
+ Modifier.padding(
+ horizontal = Dimens.selectFilterTitlePadding,
+ vertical = Dimens.selectFilterTitlePadding
+ )
+ .fillMaxWidth(),
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.icon_back),
+ contentDescription = null,
+ modifier = Modifier.size(Dimens.titleIconSize).clickable(onClick = onBackClick)
+ )
+ Text(
+ text = stringResource(R.string.filter),
+ modifier =
+ Modifier.align(Alignment.CenterVertically)
+ .weight(weight = 1f)
+ .padding(end = Dimens.titleIconSize),
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.titleLarge,
+ color = MaterialTheme.colorScheme.onPrimary
+ )
+ }
+ },
+ bottomBar = {
+ Box(
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(top = Dimens.screenVerticalMargin)
+ .clickable(enabled = false, onClick = onApplyClick)
+ .background(color = backgroundColor),
+ contentAlignment = Alignment.BottomCenter
+ ) {
+ ApplyButton(
+ onClick = onApplyClick,
+ isEnabled = uiState.isApplyButtonEnabled,
+ modifier =
+ Modifier.padding(
+ start = Dimens.sideMargin,
+ end = Dimens.sideMargin,
+ bottom = Dimens.screenVerticalMargin
+ ),
+ )
+ }
+ },
+ ) { contentPadding ->
+ LazyColumn(
+ modifier = Modifier.padding(contentPadding).background(backgroundColor).fillMaxSize()
+ ) {
+ item {
+ Divider()
+ ExpandableComposeCell(
+ title = stringResource(R.string.ownership),
+ isExpanded = ownershipExpanded,
+ isEnabled = true,
+ onInfoClicked = null,
+ onCellClicked = { ownershipExpanded = !ownershipExpanded }
+ )
+ }
+ if (ownershipExpanded) {
+ item {
+ SelectableCell(
+ title = stringResource(id = R.string.any),
+ isSelected = uiState.selectedOwnership == null,
+ onCellClicked = { onSelectedOwnership(null) }
+ )
+ }
+ items(uiState.filteredOwnershipByProviders) { ownership ->
+ Divider()
+ SelectableCell(
+ title = stringResource(id = ownership.stringResource()),
+ isSelected = ownership == uiState.selectedOwnership,
+ onCellClicked = { onSelectedOwnership(ownership) }
+ )
+ }
+ }
+ item {
+ Divider()
+ ExpandableComposeCell(
+ title = stringResource(R.string.providers),
+ isExpanded = providerExpanded,
+ isEnabled = true,
+ onInfoClicked = null,
+ onCellClicked = { providerExpanded = !providerExpanded }
+ )
+ }
+ if (providerExpanded) {
+ item {
+ Divider()
+ CheckboxCell(
+ providerName = stringResource(R.string.all_providers),
+ checked = uiState.isAllProvidersChecked,
+ onCheckedChange = { isChecked -> onAllProviderCheckChange(isChecked) }
+ )
+ }
+ items(uiState.filteredProvidersByOwnership) { provider ->
+ Divider()
+ CheckboxCell(
+ providerName = provider.name,
+ checked = provider in uiState.selectedProviders,
+ onCheckedChange = { checked -> onSelectedProviders(checked, provider) }
+ )
+ }
+ }
+ }
+ }
+}
+
+private fun Ownership.stringResource(): Int =
+ when (this) {
+ Ownership.MullvadOwned -> R.string.mullvad_owned_only
+ Ownership.Rented -> R.string.rented_only
+ }
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/FilterConstrainExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/FilterConstrainExtensions.kt
new file mode 100644
index 000000000000..8a65c64b0196
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/FilterConstrainExtensions.kt
@@ -0,0 +1,34 @@
+package net.mullvad.mullvadvpn.compose.state
+
+import net.mullvad.mullvadvpn.model.Constraint
+import net.mullvad.mullvadvpn.model.Ownership
+import net.mullvad.mullvadvpn.model.Providers
+import net.mullvad.mullvadvpn.relaylist.Provider
+
+fun Constraint.toNullableOwnership(): Ownership? =
+ when (this) {
+ is Constraint.Any -> null
+ is Constraint.Only -> this.value
+ }
+
+fun Ownership?.toOwnershipConstraint(): Constraint =
+ when (this) {
+ null -> Constraint.Any()
+ else -> Constraint.Only(this)
+ }
+
+fun Constraint.toSelectedProviders(allProviders: List): List =
+ when (this) {
+ is Constraint.Any -> allProviders
+ is Constraint.Only ->
+ this.value.providers.toList().mapNotNull { providerName ->
+ allProviders.firstOrNull { it.name == providerName }
+ }
+ }
+
+fun List.toConstraintProviders(allProviders: List): Constraint =
+ if (size == allProviders.size) {
+ Constraint.Any()
+ } else {
+ Constraint.Only(Providers(map { provider -> provider.name }.toHashSet()))
+ }
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/RelayFilterState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/RelayFilterState.kt
new file mode 100644
index 000000000000..664f03ce40e8
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/RelayFilterState.kt
@@ -0,0 +1,35 @@
+package net.mullvad.mullvadvpn.compose.state
+
+import net.mullvad.mullvadvpn.model.Ownership
+import net.mullvad.mullvadvpn.relaylist.Provider
+
+data class RelayFilterState(
+ val selectedOwnership: Ownership? = null,
+ val allProviders: List = emptyList(),
+ val selectedProviders: List = allProviders
+) {
+ val isApplyButtonEnabled = selectedProviders.isNotEmpty()
+
+ val filteredOwnershipByProviders =
+ if (selectedProviders.isEmpty()) {
+ Ownership.entries
+ } else {
+ Ownership.entries.filter { ownership ->
+ selectedProviders.any { provider ->
+ if (provider.mullvadOwned) {
+ ownership == Ownership.MullvadOwned
+ } else {
+ ownership == Ownership.Rented
+ }
+ }
+ }
+ }
+ val filteredProvidersByOwnership =
+ when (selectedOwnership) {
+ Ownership.MullvadOwned -> allProviders.filter { it.mullvadOwned }
+ Ownership.Rented -> allProviders.filterNot { it.mullvadOwned }
+ else -> allProviders
+ }
+
+ val isAllProvidersChecked = allProviders.size == selectedProviders.size
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/FilterFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/FilterFragment.kt
new file mode 100644
index 000000000000..17357d8cbfd4
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/FilterFragment.kt
@@ -0,0 +1,42 @@
+package net.mullvad.mullvadvpn.ui.fragment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.platform.ComposeView
+import androidx.fragment.app.Fragment
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.compose.screen.FilterScreen
+import net.mullvad.mullvadvpn.lib.theme.AppTheme
+import net.mullvad.mullvadvpn.viewmodel.FilterViewModel
+import org.koin.androidx.viewmodel.ext.android.viewModel
+
+class FilterFragment : Fragment() {
+
+ private val vm by viewModel()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return inflater.inflate(R.layout.fragment_compose, container, false).apply {
+ findViewById(R.id.compose_view).setContent {
+ AppTheme {
+ val state = vm.uiState.collectAsState().value
+ FilterScreen(
+ uiState = state,
+ onSelectedOwnership = vm::setSelectedOwnership,
+ onAllProviderCheckChange = vm::setAllProviders,
+ onSelectedProviders = vm::setSelectedProvider,
+ uiCloseAction = vm.uiSideEffect,
+ onBackClick = { activity?.onBackPressedDispatcher?.onBackPressed() },
+ onApplyClick = vm::onApplyButtonClicked
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModel.kt
new file mode 100644
index 000000000000..9178a221102a
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModel.kt
@@ -0,0 +1,107 @@
+package net.mullvad.mullvadvpn.viewmodel
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import net.mullvad.mullvadvpn.compose.state.RelayFilterState
+import net.mullvad.mullvadvpn.compose.state.toConstraintProviders
+import net.mullvad.mullvadvpn.compose.state.toNullableOwnership
+import net.mullvad.mullvadvpn.compose.state.toOwnershipConstraint
+import net.mullvad.mullvadvpn.compose.state.toSelectedProviders
+import net.mullvad.mullvadvpn.model.Ownership
+import net.mullvad.mullvadvpn.relaylist.Provider
+import net.mullvad.mullvadvpn.usecase.RelayListFilterUseCase
+
+class FilterViewModel(
+ private val relayListFilterUseCase: RelayListFilterUseCase,
+) : ViewModel() {
+ private val _uiSideEffect = MutableSharedFlow()
+ val uiSideEffect = _uiSideEffect.asSharedFlow()
+
+ private val selectedOwnership = MutableStateFlow(null)
+ private val selectedProviders = MutableStateFlow>(emptyList())
+
+ init {
+ viewModelScope.launch {
+ selectedProviders.value =
+ combine(
+ relayListFilterUseCase.availableProviders(),
+ relayListFilterUseCase.selectedProviders(),
+ ) { allProviders, selectedConstraintProviders ->
+ selectedConstraintProviders.toSelectedProviders(allProviders)
+ }
+ .first()
+
+ val ownershipConstraint = relayListFilterUseCase.selectedOwnership().first()
+ selectedOwnership.value = ownershipConstraint.toNullableOwnership()
+ }
+ }
+
+ val uiState: StateFlow =
+ combine(
+ selectedOwnership,
+ relayListFilterUseCase.availableProviders(),
+ selectedProviders,
+ ) { selectedOwnership, allProviders, selectedProviders ->
+ RelayFilterState(
+ selectedOwnership = selectedOwnership,
+ allProviders = allProviders,
+ selectedProviders = selectedProviders
+ )
+ }
+ .stateIn(
+ viewModelScope,
+ SharingStarted.WhileSubscribed(),
+ RelayFilterState(
+ allProviders = emptyList(),
+ selectedOwnership = null,
+ selectedProviders = emptyList()
+ ),
+ )
+
+ fun setSelectedOwnership(ownership: Ownership?) {
+ selectedOwnership.value = ownership
+ }
+
+ fun setSelectedProvider(checked: Boolean, provider: Provider) {
+ selectedProviders.value =
+ if (checked) {
+ selectedProviders.value + provider
+ } else {
+ selectedProviders.value - provider
+ }
+ }
+
+ fun setAllProviders(isChecked: Boolean) {
+ viewModelScope.launch {
+ selectedProviders.value =
+ if (isChecked) {
+ relayListFilterUseCase.availableProviders().first()
+ } else {
+ emptyList()
+ }
+ }
+ }
+
+ fun onApplyButtonClicked() {
+ val newSelectedOwnership = selectedOwnership.value.toOwnershipConstraint()
+ val newSelectedProviders =
+ selectedProviders.value.toConstraintProviders(uiState.value.allProviders)
+
+ viewModelScope.launch {
+ relayListFilterUseCase.updateOwnershipAndProviderFilter(
+ newSelectedOwnership,
+ newSelectedProviders
+ )
+ _uiSideEffect.emit(Unit)
+ }
+ }
+}
From ba4658d9545d926bfe08341a5bc80c63b3a09d3e Mon Sep 17 00:00:00 2001
From: MaryamShaghaghi <122574719+MaryamShaghaghi@users.noreply.github.com>
Date: Mon, 27 Nov 2023 14:35:46 +0100
Subject: [PATCH 5/7] Update select location screen
Co-Authored-By: Boban Sijuk <49131853+boki91@users.noreply.github.com>
---
.../compose/screen/SelectLocationScreen.kt | 123 ++++++++++++------
.../compose/state/SelectLocationUiState.kt | 15 ++-
.../compose/textfield/SearchTextField.kt | 18 ++-
.../net/mullvad/mullvadvpn/di/UiModule.kt | 6 +-
.../net/mullvad/mullvadvpn/ui/MainActivity.kt | 16 ++-
.../ui/fragment/SelectLocationFragment.kt | 8 ++
.../viewmodel/SelectLocationViewModel.kt | 101 ++++++++++++--
.../main/res/drawable/icons_more_circle.xml | 13 ++
8 files changed, 238 insertions(+), 62 deletions(-)
create mode 100644 android/lib/resource/src/main/res/drawable/icons_more_circle.xml
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreen.kt
index c09a0b986a61..55936b392a4f 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreen.kt
@@ -30,16 +30,16 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.sp
import androidx.core.text.HtmlCompat
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.compose.cell.FilterCell
import net.mullvad.mullvadvpn.compose.cell.RelayLocationCell
import net.mullvad.mullvadvpn.compose.component.MullvadCircularProgressIndicatorLarge
import net.mullvad.mullvadvpn.compose.component.drawVerticalScrollbar
@@ -60,8 +60,11 @@ import net.mullvad.mullvadvpn.relaylist.RelayItem
private fun PreviewSelectLocationScreen() {
val state =
SelectLocationUiState.ShowData(
+ searchTerm = "",
countries = listOf(RelayCountry("Country 1", "Code 1", false, emptyList())),
- selectedRelay = null
+ selectedRelay = null,
+ selectedOwnership = null,
+ selectedProvidersCount = 0
)
AppTheme {
SelectLocationScreen(
@@ -80,8 +83,12 @@ fun SelectLocationScreen(
enterTransitionEndAction: SharedFlow,
onSelectRelay: (item: RelayItem) -> Unit = {},
onSearchTermInput: (searchTerm: String) -> Unit = {},
- onBackClick: () -> Unit = {}
+ onBackClick: () -> Unit = {},
+ onFilterClick: () -> Unit = {},
+ removeOwnershipFilter: () -> Unit = {},
+ removeProviderFilter: () -> Unit = {}
) {
+
val backgroundColor = MaterialTheme.colorScheme.background
val systemUiController = rememberSystemUiController()
@@ -121,10 +128,29 @@ fun SelectLocationScreen(
.weight(weight = 1f)
.padding(end = Dimens.titleIconSize),
textAlign = TextAlign.Center,
- style = MaterialTheme.typography.headlineSmall.copy(fontSize = 20.sp),
+ style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onPrimary
)
+ Image(
+ painter = painterResource(id = R.drawable.icons_more_circle),
+ contentDescription = null,
+ modifier = Modifier.size(Dimens.titleIconSize).clickable { onFilterClick() }
+ )
+ }
+ when (uiState) {
+ SelectLocationUiState.Loading -> {}
+ is SelectLocationUiState.ShowData -> {
+ if (uiState.hasFilter) {
+ FilterCell(
+ ownershipFilter = uiState.selectedOwnership,
+ selectedProviderFilter = uiState.selectedProvidersCount,
+ removeOwnershipFilter = removeOwnershipFilter,
+ removeProviderFilter = removeProviderFilter
+ )
+ }
+ }
}
+
SearchTextField(
modifier =
Modifier.fillMaxWidth()
@@ -146,7 +172,7 @@ fun SelectLocationScreen(
MaterialTheme.colorScheme.onBackground.copy(alpha = AlphaScrollbar)
),
state = lazyListState,
- horizontalAlignment = Alignment.CenterHorizontally
+ horizontalAlignment = Alignment.CenterHorizontally,
) {
when (uiState) {
SelectLocationUiState.Loading -> {
@@ -157,45 +183,56 @@ fun SelectLocationScreen(
}
}
is SelectLocationUiState.ShowData -> {
- items(
- count = uiState.countries.size,
- key = { index -> uiState.countries[index].hashCode() },
- contentType = { ContentType.ITEM }
- ) { index ->
- val country = uiState.countries[index]
- RelayLocationCell(
- relay = country,
- selectedItem = uiState.selectedRelay,
- onSelectRelay = onSelectRelay,
- modifier = Modifier.animateContentSize()
- )
- }
- }
- is SelectLocationUiState.NoSearchResultFound -> {
- item(contentType = ContentType.EMPTY_TEXT) {
- val firstRow =
- HtmlCompat.fromHtml(
- textResource(
- id = R.string.select_location_empty_text_first_row,
- uiState.searchTerm
- ),
- HtmlCompat.FROM_HTML_MODE_COMPACT
- )
- .toAnnotatedString(boldFontWeight = FontWeight.ExtraBold)
- Text(
- text =
- buildAnnotatedString {
- append(firstRow)
- appendLine()
- append(
+ if (uiState.countries.isEmpty()) {
+ item(contentType = ContentType.EMPTY_TEXT) {
+ val firstRow =
+ HtmlCompat.fromHtml(
textResource(
- id = R.string.select_location_empty_text_second_row
- )
+ id = R.string.select_location_empty_text_first_row,
+ uiState.searchTerm
+ ),
+ HtmlCompat.FROM_HTML_MODE_COMPACT
)
- },
- style = MaterialTheme.typography.labelMedium,
- textAlign = TextAlign.Center
- )
+ .toAnnotatedString(boldFontWeight = FontWeight.ExtraBold)
+ val secondRow =
+ textResource(id = R.string.select_location_empty_text_second_row)
+ Column(
+ modifier =
+ Modifier.padding(
+ horizontal = Dimens.selectLocationTitlePadding
+ ),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = firstRow,
+ style = MaterialTheme.typography.labelMedium,
+ textAlign = TextAlign.Center,
+ color = MaterialTheme.colorScheme.onSecondary,
+ maxLines = 2,
+ overflow = TextOverflow.Ellipsis
+ )
+ Text(
+ text = secondRow,
+ style = MaterialTheme.typography.labelMedium,
+ textAlign = TextAlign.Center,
+ color = MaterialTheme.colorScheme.onSecondary
+ )
+ }
+ }
+ } else {
+ items(
+ count = uiState.countries.size,
+ key = { index -> uiState.countries[index].hashCode() },
+ contentType = { ContentType.ITEM }
+ ) { index ->
+ val country = uiState.countries[index]
+ RelayLocationCell(
+ relay = country,
+ selectedItem = uiState.selectedRelay,
+ onSelectRelay = onSelectRelay,
+ modifier = Modifier.animateContentSize()
+ )
+ }
}
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SelectLocationUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SelectLocationUiState.kt
index fece45f0aa91..123bf821e605 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SelectLocationUiState.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/SelectLocationUiState.kt
@@ -1,13 +1,20 @@
package net.mullvad.mullvadvpn.compose.state
+import net.mullvad.mullvadvpn.model.Ownership
import net.mullvad.mullvadvpn.relaylist.RelayCountry
import net.mullvad.mullvadvpn.relaylist.RelayItem
sealed interface SelectLocationUiState {
- data object Loading : SelectLocationUiState
- data class ShowData(val countries: List, val selectedRelay: RelayItem?) :
- SelectLocationUiState
+ data object Loading : SelectLocationUiState
- data class NoSearchResultFound(val searchTerm: String) : SelectLocationUiState
+ data class ShowData(
+ val searchTerm: String,
+ val countries: List,
+ val selectedRelay: RelayItem?,
+ val selectedOwnership: Ownership?,
+ val selectedProvidersCount: Int?
+ ) : SelectLocationUiState {
+ val hasFilter: Boolean = (selectedProvidersCount != null || selectedOwnership != null)
+ }
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/textfield/SearchTextField.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/textfield/SearchTextField.kt
index 2f743a8d2303..bbee4a969bc1 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/textfield/SearchTextField.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/textfield/SearchTextField.kt
@@ -2,6 +2,7 @@ package net.mullvad.mullvadvpn.compose.textfield
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -80,15 +81,28 @@ fun SearchTextField(
modifier =
Modifier.size(
width = Dimens.searchIconSize,
- height = Dimens.searchIconSize
+ height = Dimens.searchIconSize,
),
colorFilter =
- ColorFilter.tint(color = MaterialTheme.colorScheme.onSecondary)
+ ColorFilter.tint(color = MaterialTheme.colorScheme.onSecondary),
)
},
placeholder = {
Text(text = placeHolder, style = MaterialTheme.typography.labelLarge)
},
+ trailingIcon = {
+ if (searchTerm.isNotEmpty()) {
+ Image(
+ modifier =
+ Modifier.size(Dimens.smallIconSize).clickable {
+ searchTerm = ""
+ onValueChange.invoke(searchTerm)
+ },
+ painter = painterResource(id = R.drawable.icon_close),
+ contentDescription = null,
+ )
+ }
+ },
shape = MaterialTheme.shapes.medium,
colors =
TextFieldDefaults.colors(
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
index b39d16b0aaa5..220097a7313a 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
@@ -29,6 +29,7 @@ import net.mullvad.mullvadvpn.usecase.NewDeviceNotificationUseCase
import net.mullvad.mullvadvpn.usecase.PaymentUseCase
import net.mullvad.mullvadvpn.usecase.PlayPaymentUseCase
import net.mullvad.mullvadvpn.usecase.PortRangeUseCase
+import net.mullvad.mullvadvpn.usecase.RelayListFilterUseCase
import net.mullvad.mullvadvpn.usecase.RelayListUseCase
import net.mullvad.mullvadvpn.usecase.TunnelStateNotificationUseCase
import net.mullvad.mullvadvpn.usecase.VersionNotificationUseCase
@@ -39,6 +40,7 @@ import net.mullvad.mullvadvpn.viewmodel.ChangelogViewModel
import net.mullvad.mullvadvpn.viewmodel.ConnectViewModel
import net.mullvad.mullvadvpn.viewmodel.DeviceListViewModel
import net.mullvad.mullvadvpn.viewmodel.DeviceRevokedViewModel
+import net.mullvad.mullvadvpn.viewmodel.FilterViewModel
import net.mullvad.mullvadvpn.viewmodel.LoginViewModel
import net.mullvad.mullvadvpn.viewmodel.OutOfTimeViewModel
import net.mullvad.mullvadvpn.viewmodel.PrivacyDisclaimerViewModel
@@ -103,6 +105,7 @@ val uiModule = module {
single { ChangelogDataProvider(get()) }
+ single { RelayListFilterUseCase(get(), get()) }
single { RelayListListener(get()) }
// Will be resolved using from either of the two PaymentModule.kt classes.
@@ -129,7 +132,7 @@ val uiModule = module {
viewModel { DeviceRevokedViewModel(get(), get()) }
viewModel { LoginViewModel(get(), get(), get()) }
viewModel { PrivacyDisclaimerViewModel(get()) }
- viewModel { SelectLocationViewModel(get(), get()) }
+ viewModel { SelectLocationViewModel(get(), get(), get()) }
viewModel { SettingsViewModel(get(), get()) }
viewModel { VoucherDialogViewModel(get(), get()) }
viewModel { VpnSettingsViewModel(get(), get(), get(), get(), get()) }
@@ -137,6 +140,7 @@ val uiModule = module {
viewModel { ReportProblemViewModel(get(), get()) }
viewModel { ViewLogsViewModel(get()) }
viewModel { OutOfTimeViewModel(get(), get(), get(), get()) }
+ viewModel { FilterViewModel(get()) }
}
const val SELF_PACKAGE_NAME = "SELF_PACKAGE_NAME"
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt
index f299b8c956bd..f5e24dacf187 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt
@@ -42,6 +42,7 @@ import net.mullvad.mullvadvpn.repository.PrivacyDisclaimerRepository
import net.mullvad.mullvadvpn.ui.fragment.AccountFragment
import net.mullvad.mullvadvpn.ui.fragment.ConnectFragment
import net.mullvad.mullvadvpn.ui.fragment.DeviceRevokedFragment
+import net.mullvad.mullvadvpn.ui.fragment.FilterFragment
import net.mullvad.mullvadvpn.ui.fragment.LoadingFragment
import net.mullvad.mullvadvpn.ui.fragment.LoginFragment
import net.mullvad.mullvadvpn.ui.fragment.OutOfTimeFragment
@@ -55,7 +56,6 @@ import net.mullvad.mullvadvpn.viewmodel.ChangelogDialogUiState
import net.mullvad.mullvadvpn.viewmodel.ChangelogViewModel
import org.koin.android.ext.android.getKoin
import org.koin.core.context.loadKoinModules
-import org.koin.dsl.bind
open class MainActivity : FragmentActivity() {
private val requestNotificationPermissionLauncher =
@@ -174,6 +174,20 @@ open class MainActivity : FragmentActivity() {
}
}
+ fun openFilter() {
+ supportFragmentManager.beginTransaction().apply {
+ setCustomAnimations(
+ R.anim.fragment_enter_from_right,
+ R.anim.do_nothing,
+ R.anim.do_nothing,
+ R.anim.fragment_exit_to_right
+ )
+ replace(R.id.main_fragment, FilterFragment())
+ addToBackStack(null)
+ commitAllowingStateLoss()
+ }
+ }
+
private fun launchDeviceStateHandler(): Job {
return lifecycleScope.launch {
launch {
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/SelectLocationFragment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/SelectLocationFragment.kt
index d1c4ac72bfbf..64fdee71f625 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/SelectLocationFragment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/fragment/SelectLocationFragment.kt
@@ -9,6 +9,7 @@ import androidx.compose.ui.platform.ComposeView
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.screen.SelectLocationScreen
import net.mullvad.mullvadvpn.lib.theme.AppTheme
+import net.mullvad.mullvadvpn.ui.MainActivity
import net.mullvad.mullvadvpn.viewmodel.SelectLocationViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel
@@ -32,12 +33,19 @@ class SelectLocationFragment : BaseFragment() {
onSelectRelay = vm::selectRelay,
onSearchTermInput = vm::onSearchTermInput,
onBackClick = { activity?.onBackPressedDispatcher?.onBackPressed() },
+ removeOwnershipFilter = vm::removeOwnerFilter,
+ removeProviderFilter = vm::removeProviderFilter,
+ onFilterClick = ::openFilterView
)
}
}
}
}
+ private fun openFilterView() {
+ (context as? MainActivity)?.openFilter()
+ }
+
override fun onEnterTransitionAnimationEnd() {
vm.onTransitionAnimationEnd()
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt
index 5e95674e0a6a..caddae313bdc 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt
@@ -7,37 +7,75 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.compose.state.SelectLocationUiState
+import net.mullvad.mullvadvpn.compose.state.toNullableOwnership
+import net.mullvad.mullvadvpn.compose.state.toSelectedProviders
+import net.mullvad.mullvadvpn.model.Constraint
+import net.mullvad.mullvadvpn.model.Ownership
+import net.mullvad.mullvadvpn.relaylist.Provider
import net.mullvad.mullvadvpn.relaylist.RelayItem
import net.mullvad.mullvadvpn.relaylist.filterOnSearchTerm
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.connectionProxy
+import net.mullvad.mullvadvpn.usecase.RelayListFilterUseCase
import net.mullvad.mullvadvpn.usecase.RelayListUseCase
class SelectLocationViewModel(
private val serviceConnectionManager: ServiceConnectionManager,
- private val relayListUseCase: RelayListUseCase
+ private val relayListUseCase: RelayListUseCase,
+ private val relayListFilterUseCase: RelayListFilterUseCase
) : ViewModel() {
+
private val _closeAction = MutableSharedFlow()
private val _enterTransitionEndAction = MutableSharedFlow()
private val _searchTerm = MutableStateFlow(EMPTY_SEARCH_TERM)
val uiState =
- combine(relayListUseCase.relayListWithSelection(), _searchTerm) {
+ combine(
+ relayListUseCase.relayListWithSelection(),
+ _searchTerm,
+ relayListFilterUseCase.selectedOwnership(),
+ relayListFilterUseCase.availableProviders(),
+ relayListFilterUseCase.selectedProviders()
+ ) {
(relayCountries, relayItem),
- searchTerm ->
+ searchTerm,
+ selectedOwnership,
+ allProviders,
+ selectedConstraintProviders ->
+ val selectedProviders =
+ selectedConstraintProviders.toSelectedProviders(allProviders)
+
+ val selectedProvidersByOwnershipList =
+ filterSelectedProvidersByOwnership(
+ selectedProviders,
+ selectedOwnership.toNullableOwnership()
+ )
+
+ val allProvidersByOwnershipListList =
+ filterAllProvidersByOwnership(
+ allProviders,
+ selectedOwnership.toNullableOwnership()
+ )
+
val filteredRelayCountries =
relayCountries.filterOnSearchTerm(searchTerm, relayItem)
- if (searchTerm.isNotEmpty() && filteredRelayCountries.isEmpty()) {
- SelectLocationUiState.NoSearchResultFound(searchTerm = searchTerm)
- } else {
- SelectLocationUiState.ShowData(
- countries = filteredRelayCountries,
- selectedRelay = relayItem
- )
- }
+ SelectLocationUiState.ShowData(
+ searchTerm = searchTerm,
+ countries = filteredRelayCountries,
+ selectedRelay = relayItem,
+ selectedOwnership = selectedOwnership.toNullableOwnership(),
+ selectedProvidersCount =
+ if (
+ selectedProvidersByOwnershipList.size ==
+ allProvidersByOwnershipListList.size
+ )
+ null
+ else selectedProvidersByOwnershipList.size
+ )
}
.stateIn(
viewModelScope,
@@ -47,6 +85,7 @@ class SelectLocationViewModel(
@Suppress("konsist.ensure public properties use permitted names")
val uiCloseAction = _closeAction.asSharedFlow()
+
@Suppress("konsist.ensure public properties use permitted names")
val enterTransitionEndAction = _enterTransitionEndAction.asSharedFlow()
@@ -64,6 +103,46 @@ class SelectLocationViewModel(
viewModelScope.launch { _searchTerm.emit(searchTerm) }
}
+ private fun filterSelectedProvidersByOwnership(
+ selectedProviders: List,
+ selectedOwnership: Ownership?
+ ): List {
+ return when (selectedOwnership) {
+ Ownership.MullvadOwned -> selectedProviders.filter { it.mullvadOwned }
+ Ownership.Rented -> selectedProviders.filterNot { it.mullvadOwned }
+ else -> selectedProviders
+ }
+ }
+
+ private fun filterAllProvidersByOwnership(
+ allProviders: List,
+ selectedOwnership: Ownership?
+ ): List {
+ return when (selectedOwnership) {
+ Ownership.MullvadOwned -> allProviders.filter { it.mullvadOwned }
+ Ownership.Rented -> allProviders.filterNot { it.mullvadOwned }
+ else -> allProviders
+ }
+ }
+
+ fun removeOwnerFilter() {
+ viewModelScope.launch {
+ relayListFilterUseCase.updateOwnershipAndProviderFilter(
+ Constraint.Any(),
+ relayListFilterUseCase.selectedProviders().first()
+ )
+ }
+ }
+
+ fun removeProviderFilter() {
+ viewModelScope.launch {
+ relayListFilterUseCase.updateOwnershipAndProviderFilter(
+ relayListFilterUseCase.selectedOwnership().first(),
+ Constraint.Any()
+ )
+ }
+ }
+
companion object {
private const val EMPTY_SEARCH_TERM = ""
}
diff --git a/android/lib/resource/src/main/res/drawable/icons_more_circle.xml b/android/lib/resource/src/main/res/drawable/icons_more_circle.xml
new file mode 100644
index 000000000000..2f7800ccf377
--- /dev/null
+++ b/android/lib/resource/src/main/res/drawable/icons_more_circle.xml
@@ -0,0 +1,13 @@
+
+
+
From 1e64dcdbd9d82173c65a4d4b49f72f80f7de8269 Mon Sep 17 00:00:00 2001
From: MaryamShaghaghi <122574719+MaryamShaghaghi@users.noreply.github.com>
Date: Mon, 27 Nov 2023 14:37:32 +0100
Subject: [PATCH 6/7] Add filter screen and viewmodel tests
Co-Authored-By: Boban Sijuk <49131853+boki91@users.noreply.github.com>
---
.../compose/screen/FilterScreenTest.kt | 191 ++++++++++++++++++
.../viewmodel/FilterViewModelTest.kt | 129 ++++++++++++
2 files changed, 320 insertions(+)
create mode 100644 android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreenTest.kt
create mode 100644 android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModelTest.kt
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreenTest.kt
new file mode 100644
index 000000000000..b5f762b89b22
--- /dev/null
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/FilterScreenTest.kt
@@ -0,0 +1,191 @@
+package net.mullvad.mullvadvpn.compose.screen
+
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import io.mockk.MockKAnnotations
+import io.mockk.mockk
+import io.mockk.verify
+import kotlinx.coroutines.flow.MutableSharedFlow
+import net.mullvad.mullvadvpn.compose.setContentWithTheme
+import net.mullvad.mullvadvpn.compose.state.RelayFilterState
+import net.mullvad.mullvadvpn.model.Ownership
+import net.mullvad.mullvadvpn.relaylist.Provider
+import org.junit.Rule
+import org.junit.Test
+
+class FilterScreenTest {
+ @get:Rule val composeTestRule = createComposeRule()
+
+ fun setup() {
+ MockKAnnotations.init(this)
+ }
+
+ @Test
+ fun testDefaultState() {
+ composeTestRule.setContentWithTheme {
+ FilterScreen(
+ uiState =
+ RelayFilterState(
+ allProviders = DUMMY_RELAY_ALL_PROVIDERS,
+ selectedOwnership = null,
+ selectedProviders = DUMMY_SELECTED_PROVIDERS,
+ ),
+ uiCloseAction = MutableSharedFlow(),
+ onSelectedProviders = { _, _ -> }
+ )
+ }
+ composeTestRule.apply {
+ onNodeWithText("Ownership").assertExists()
+ onNodeWithText("Providers").assertExists()
+ }
+ }
+
+ @Test
+ fun testIsAnyCellShowing() {
+ composeTestRule.setContentWithTheme {
+ FilterScreen(
+ uiState =
+ RelayFilterState(
+ allProviders = DUMMY_RELAY_ALL_PROVIDERS,
+ selectedOwnership = null,
+ selectedProviders = DUMMY_SELECTED_PROVIDERS
+ ),
+ uiCloseAction = MutableSharedFlow(),
+ onSelectedProviders = { _, _ -> }
+ )
+ }
+ composeTestRule.apply {
+ onNodeWithText("Ownership").performClick()
+ onNodeWithText("Any").assertExists()
+ }
+ }
+
+ @Test
+ fun testIsMullvadCellShowing() {
+ composeTestRule.setContentWithTheme {
+ FilterScreen(
+ uiState =
+ RelayFilterState(
+ allProviders = DUMMY_RELAY_ALL_PROVIDERS,
+ selectedOwnership = Ownership.MullvadOwned,
+ selectedProviders = DUMMY_SELECTED_PROVIDERS
+ ),
+ uiCloseAction = MutableSharedFlow(),
+ onSelectedProviders = { _, _ -> }
+ )
+ }
+ composeTestRule.apply {
+ onNodeWithText("Ownership").performClick()
+ onNodeWithText("Mullvad owned only").assertExists()
+ }
+ }
+
+ @Test
+ fun testIsRentedCellShowing() {
+ composeTestRule.setContentWithTheme {
+ FilterScreen(
+ uiState =
+ RelayFilterState(
+ allProviders = DUMMY_RELAY_ALL_PROVIDERS,
+ selectedOwnership = Ownership.Rented,
+ selectedProviders = DUMMY_SELECTED_PROVIDERS
+ ),
+ uiCloseAction = MutableSharedFlow(),
+ onSelectedProviders = { _, _ -> }
+ )
+ }
+ composeTestRule.apply {
+ onNodeWithText("Ownership").performClick()
+ onNodeWithText("Rented only").assertExists()
+ }
+ }
+
+ @Test
+ fun testShowProviders() {
+ composeTestRule.setContentWithTheme {
+ FilterScreen(
+ uiState =
+ RelayFilterState(
+ allProviders = DUMMY_RELAY_ALL_PROVIDERS,
+ selectedOwnership = null,
+ selectedProviders = DUMMY_SELECTED_PROVIDERS
+ ),
+ uiCloseAction = MutableSharedFlow(),
+ onSelectedProviders = { _, _ -> }
+ )
+ }
+
+ composeTestRule.apply {
+ onNodeWithText("Providers").performClick()
+ onNodeWithText("Creanova").assertExists()
+ onNodeWithText("Creanova").assertExists()
+ onNodeWithText("100TB").assertExists()
+ }
+ }
+
+ @Test
+ fun testApplyButtonClick() {
+ val mockClickListener: () -> Unit = mockk(relaxed = true)
+ composeTestRule.setContentWithTheme {
+ FilterScreen(
+ uiState =
+ RelayFilterState(
+ allProviders = listOf(),
+ selectedOwnership = null,
+ selectedProviders = listOf(Provider("31173", true))
+ ),
+ uiCloseAction = MutableSharedFlow(),
+ onSelectedProviders = { _, _ -> },
+ onApplyClick = mockClickListener
+ )
+ }
+ composeTestRule.onNodeWithText("Apply").performClick()
+ verify { mockClickListener() }
+ }
+
+ companion object {
+
+ private val DUMMY_RELAY_ALL_PROVIDERS =
+ listOf(
+ Provider("31173", true),
+ Provider("100TB", false),
+ Provider("Blix", true),
+ Provider("Creanova", true),
+ Provider("DataPacket", false),
+ Provider("HostRoyale", false),
+ Provider("hostuniversal", false),
+ Provider("iRegister", false),
+ Provider("M247", false),
+ Provider("Makonix", false),
+ Provider("PrivateLayer", false),
+ Provider("ptisp", false),
+ Provider("Qnax", false),
+ Provider("Quadranet", false),
+ Provider("techfutures", false),
+ Provider("Tzulo", false),
+ Provider("xtom", false)
+ )
+
+ private val DUMMY_SELECTED_PROVIDERS =
+ listOf(
+ Provider("31173", true),
+ Provider("100TB", false),
+ Provider("Blix", true),
+ Provider("Creanova", true),
+ Provider("DataPacket", false),
+ Provider("HostRoyale", false),
+ Provider("hostuniversal", false),
+ Provider("iRegister", false),
+ Provider("M247", false),
+ Provider("Makonix", false),
+ Provider("PrivateLayer", false),
+ Provider("ptisp", false),
+ Provider("Qnax", false),
+ Provider("Quadranet", false),
+ Provider("techfutures", false),
+ Provider("Tzulo", false),
+ Provider("xtom", false)
+ )
+ }
+}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModelTest.kt
new file mode 100644
index 000000000000..9f61ea4a91b2
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModelTest.kt
@@ -0,0 +1,129 @@
+package net.mullvad.mullvadvpn.viewmodel
+
+import androidx.lifecycle.viewModelScope
+import app.cash.turbine.test
+import io.mockk.coVerify
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.unmockkAll
+import kotlin.test.assertEquals
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import net.mullvad.mullvadvpn.compose.state.toConstraintProviders
+import net.mullvad.mullvadvpn.compose.state.toOwnershipConstraint
+import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
+import net.mullvad.mullvadvpn.lib.common.test.assertLists
+import net.mullvad.mullvadvpn.model.Constraint
+import net.mullvad.mullvadvpn.model.Ownership
+import net.mullvad.mullvadvpn.model.Providers
+import net.mullvad.mullvadvpn.relaylist.Provider
+import net.mullvad.mullvadvpn.usecase.RelayListFilterUseCase
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class FilterViewModelTest {
+ @get:Rule val testCoroutineRule = TestCoroutineRule()
+ private val mockRelayListFilterUseCase: RelayListFilterUseCase = mockk(relaxed = true)
+ private lateinit var viewModel: FilterViewModel
+ private val selectedOwnership =
+ MutableStateFlow>(Constraint.Only(Ownership.MullvadOwned))
+ private val dummyListOfAllProviders =
+ listOf(
+ Provider("31173", true),
+ Provider("100TB", false),
+ Provider("Blix", true),
+ Provider("Creanova", true),
+ Provider("DataPacket", false),
+ Provider("HostRoyale", false),
+ Provider("hostuniversal", false),
+ Provider("iRegister", false),
+ Provider("M247", false),
+ Provider("Makonix", false),
+ Provider("PrivateLayer", false),
+ Provider("ptisp", false),
+ Provider("Qnax", false),
+ Provider("Quadranet", false),
+ Provider("techfutures", false),
+ Provider("Tzulo", false),
+ Provider("xtom", false)
+ )
+ private val mockSelectedProviders: List =
+ listOf(Provider("31173", true), Provider("Blix", true), Provider("Creanova", true))
+
+ @Before
+ fun setup() {
+ every { mockRelayListFilterUseCase.selectedOwnership() } returns selectedOwnership
+ every { mockRelayListFilterUseCase.availableProviders() } returns
+ flowOf(dummyListOfAllProviders)
+ every { mockRelayListFilterUseCase.selectedProviders() } returns
+ flowOf(Constraint.Only(Providers(mockSelectedProviders.map { it.name }.toHashSet())))
+ viewModel = FilterViewModel(mockRelayListFilterUseCase)
+ }
+
+ @After
+ fun teardown() {
+ viewModel.viewModelScope.coroutineContext.cancel()
+ unmockkAll()
+ }
+
+ @Test
+ fun testSetSelectedOwnership() = runTest {
+ // Arrange
+ val mockOwnership = Ownership.Rented
+ // Assert
+ viewModel.uiState.test {
+ assertEquals(awaitItem().selectedOwnership, Ownership.MullvadOwned)
+ viewModel.setSelectedOwnership(mockOwnership)
+ assertEquals(mockOwnership, awaitItem().selectedOwnership)
+ }
+ }
+
+ @Test
+ fun testSetSelectedProvider() = runTest {
+ // Arrange
+ val mockSelectedProvidersList = Provider("ptisp", false)
+ // Assert
+ viewModel.uiState.test {
+ assertLists(awaitItem().selectedProviders, mockSelectedProviders)
+ viewModel.setSelectedProvider(true, mockSelectedProvidersList)
+ assertLists(
+ listOf(mockSelectedProvidersList) + mockSelectedProviders,
+ awaitItem().selectedProviders
+ )
+ }
+ }
+
+ @Test
+ fun testSetAllProviders() = runTest {
+ // Arrange
+ val mockProvidersList = dummyListOfAllProviders
+ // Act
+ viewModel.setAllProviders(true)
+ // Assert
+ viewModel.uiState.test {
+ val state = awaitItem()
+ assertEquals(mockProvidersList, state.selectedProviders)
+ }
+ }
+
+ @Test
+ fun testOnApplyButtonClicked() = runTest {
+ // Arrange
+ val mockOwnership = Ownership.MullvadOwned.toOwnershipConstraint()
+ val mockSelectedProviders =
+ mockSelectedProviders.toConstraintProviders(dummyListOfAllProviders)
+ // Act
+ viewModel.onApplyButtonClicked()
+ // Assert
+ coVerify {
+ mockRelayListFilterUseCase.updateOwnershipAndProviderFilter(
+ mockOwnership,
+ mockSelectedProviders
+ )
+ }
+ }
+}
From 62fa2db4f196adfda37b2cb0dc01492c07849e9a Mon Sep 17 00:00:00 2001
From: MaryamShaghaghi <122574719+MaryamShaghaghi@users.noreply.github.com>
Date: Mon, 27 Nov 2023 14:38:05 +0100
Subject: [PATCH 7/7] Add select location screen and viewmodel tests
Co-Authored-By: Boban Sijuk <49131853+boki91@users.noreply.github.com>
---
.../screen/SelectLocationScreenTest.kt | 29 +++++++--
.../viewmodel/SelectLocationViewModelTest.kt | 61 +++++++++++++++++--
2 files changed, 80 insertions(+), 10 deletions(-)
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreenTest.kt
index 3b5da50d3374..7e66bc24d9fa 100644
--- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreenTest.kt
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SelectLocationScreenTest.kt
@@ -56,7 +56,10 @@ class SelectLocationScreenTest {
uiState =
SelectLocationUiState.ShowData(
countries = DUMMY_RELAY_COUNTRIES,
- selectedRelay = null
+ selectedRelay = null,
+ selectedOwnership = null,
+ selectedProvidersCount = 0,
+ searchTerm = ""
),
uiCloseAction = MutableSharedFlow(),
enterTransitionEndAction = MutableSharedFlow().asSharedFlow()
@@ -93,7 +96,10 @@ class SelectLocationScreenTest {
uiState =
SelectLocationUiState.ShowData(
countries = updatedDummyList,
- selectedRelay = updatedDummyList[0].cities[0].relays[0]
+ selectedRelay = updatedDummyList[0].cities[0].relays[0],
+ selectedOwnership = null,
+ selectedProvidersCount = 0,
+ searchTerm = ""
),
uiCloseAction = MutableSharedFlow(),
enterTransitionEndAction = MutableSharedFlow().asSharedFlow()
@@ -118,7 +124,13 @@ class SelectLocationScreenTest {
composeTestRule.setContentWithTheme {
SelectLocationScreen(
uiState =
- SelectLocationUiState.ShowData(countries = emptyList(), selectedRelay = null),
+ SelectLocationUiState.ShowData(
+ countries = emptyList(),
+ selectedRelay = null,
+ selectedOwnership = null,
+ selectedProvidersCount = 0,
+ searchTerm = ""
+ ),
uiCloseAction = MutableSharedFlow(),
enterTransitionEndAction = MutableSharedFlow().asSharedFlow(),
onSearchTermInput = mockedSearchTermInput
@@ -140,7 +152,14 @@ class SelectLocationScreenTest {
val mockSearchString = "SEARCH"
composeTestRule.setContentWithTheme {
SelectLocationScreen(
- uiState = SelectLocationUiState.NoSearchResultFound(searchTerm = mockSearchString),
+ uiState =
+ SelectLocationUiState.ShowData(
+ countries = emptyList(),
+ selectedRelay = null,
+ selectedOwnership = null,
+ selectedProvidersCount = 0,
+ searchTerm = mockSearchString
+ ),
uiCloseAction = MutableSharedFlow(),
enterTransitionEndAction = MutableSharedFlow().asSharedFlow(),
onSearchTermInput = mockedSearchTermInput
@@ -187,7 +206,7 @@ class SelectLocationScreenTest {
private val DUMMY_RELAY_COUNTRIES =
RelayList(
arrayListOf(DUMMY_RELAY_COUNTRY_1, DUMMY_RELAY_COUNTRY_2),
- DUMMY_WIREGUARD_ENDPOINT_DATA
+ DUMMY_WIREGUARD_ENDPOINT_DATA,
)
.toRelayCountries(ownership = Constraint.Any(), providers = Constraint.Any())
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModelTest.kt
index 44be67fa648f..74d7d80c1901 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModelTest.kt
@@ -15,7 +15,11 @@ import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.compose.state.SelectLocationUiState
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
import net.mullvad.mullvadvpn.lib.common.test.assertLists
+import net.mullvad.mullvadvpn.model.Constraint
import net.mullvad.mullvadvpn.model.GeographicLocationConstraint
+import net.mullvad.mullvadvpn.model.Ownership
+import net.mullvad.mullvadvpn.model.Providers
+import net.mullvad.mullvadvpn.relaylist.Provider
import net.mullvad.mullvadvpn.relaylist.RelayCountry
import net.mullvad.mullvadvpn.relaylist.RelayItem
import net.mullvad.mullvadvpn.relaylist.RelayList
@@ -23,6 +27,7 @@ import net.mullvad.mullvadvpn.relaylist.filterOnSearchTerm
import net.mullvad.mullvadvpn.ui.serviceconnection.ConnectionProxy
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.connectionProxy
+import net.mullvad.mullvadvpn.usecase.RelayListFilterUseCase
import net.mullvad.mullvadvpn.usecase.RelayListUseCase
import org.junit.After
import org.junit.Before
@@ -32,21 +37,31 @@ import org.junit.Test
class SelectLocationViewModelTest {
@get:Rule val testCoroutineRule = TestCoroutineRule()
+ private val mockRelayListFilterUseCase: RelayListFilterUseCase = mockk(relaxed = true)
private val mockServiceConnectionManager: ServiceConnectionManager = mockk()
private lateinit var viewModel: SelectLocationViewModel
-
private val relayListWithSelectionFlow = MutableStateFlow(RelayList(emptyList(), null))
-
private val mockRelayListUseCase: RelayListUseCase = mockk()
+ private val selectedOwnership = MutableStateFlow>(Constraint.Any())
+ private val selectedProvider = MutableStateFlow>(Constraint.Any())
+ private val allProvider = MutableStateFlow>(emptyList())
@Before
fun setup() {
+
+ every { mockRelayListFilterUseCase.selectedOwnership() } returns selectedOwnership
+ every { mockRelayListFilterUseCase.selectedProviders() } returns selectedProvider
+ every { mockRelayListFilterUseCase.availableProviders() } returns allProvider
every { mockRelayListUseCase.relayListWithSelection() } returns relayListWithSelectionFlow
mockkStatic(SERVICE_CONNECTION_MANAGER_EXTENSIONS)
mockkStatic(RELAY_LIST_EXTENSIONS)
-
- viewModel = SelectLocationViewModel(mockServiceConnectionManager, mockRelayListUseCase)
+ viewModel =
+ SelectLocationViewModel(
+ mockServiceConnectionManager,
+ mockRelayListUseCase,
+ mockRelayListFilterUseCase
+ )
}
@After
@@ -164,11 +179,47 @@ class SelectLocationViewModelTest {
// Assert
val actualState = awaitItem()
- assertIs(actualState)
+ assertIs(actualState)
assertEquals(mockSearchString, actualState.searchTerm)
}
}
+ @Test
+ fun testRemoveOwnerFilter() = runTest {
+ // Arrange
+ val mockSelectedProviders: Constraint = mockk()
+ every { mockRelayListFilterUseCase.selectedProviders() } returns
+ MutableStateFlow(mockSelectedProviders)
+
+ // Act
+ viewModel.removeOwnerFilter()
+ // Assert
+ verify {
+ mockRelayListFilterUseCase.updateOwnershipAndProviderFilter(
+ any>(),
+ mockSelectedProviders
+ )
+ }
+ }
+
+ @Test
+ fun testRemoveProviderFilter() = runTest {
+ // Arrange
+ val mockSelectedOwnership: Constraint = mockk()
+ every { mockRelayListFilterUseCase.selectedOwnership() } returns
+ MutableStateFlow(mockSelectedOwnership)
+
+ // Act
+ viewModel.removeProviderFilter()
+ // Assert
+ verify {
+ mockRelayListFilterUseCase.updateOwnershipAndProviderFilter(
+ mockSelectedOwnership,
+ any>()
+ )
+ }
+ }
+
companion object {
private const val SERVICE_CONNECTION_MANAGER_EXTENSIONS =
"net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManagerExtensionsKt"