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