diff --git a/CHANGES.md b/CHANGES.md index 1601e0ec85..a73551cc33 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,33 @@ -Changes in Element 1.1.4 (2021-XX-XX) +Changes in Element 1.1.5 (2021-XX-XX) =================================================== Features ✨: - +Improvements 🙌: + - + +Bugfix 🐛: + - + +Translations 🗣: + - + +SDK API changes ⚠️: + - + +Build 🧱: + - + +Test: + - + +Other changes: + - + +Changes in Element 1.1.4 (2021-04-09) +=================================================== + Improvements 🙌: - Split network request `/keys/query` into smaller requests (250 users max) (#2925) - Crypto improvement | Bulk send NO_OLM withheld code @@ -14,28 +38,32 @@ Improvements 🙌: - Update reactions to Unicode 13.1 (#2998) - Be more robust when parsing some enums - Improve timeline filtering (dissociate membership and profile events, display hidden events when highlighted, fix hidden item/read receipts behavior) + - Add better support for empty room name fallback (#3106) + - Room list improvements (paging) + - Fix quick click action (#3127) + - Get Event after a Push for a faster notification display in some conditions + - Always try to retry Http requests in case of 429 (#1300) + - registration availability endpoint added to matrix-sdk Bugfix 🐛: - Fix bad theme change for the MainActivity - Handle encrypted reactions (#2509) - Disable URL preview for some domains (#2995) - Fix avatar rendering for DMs, after initial sync (#2693) - -Translations 🗣: - - + - Fix mandatory parameter in API (#3065) + - If signout request fails, do not start LoginActivity, but restart the app (#3099) + - Retain keyword order in emoji import script, and update the generated file (#3147) SDK API changes ⚠️: - Several Services have been migrated to coroutines (#2449) - Removes filtering options on Timeline. Build 🧱: - - - -Test: - - + - Properly exclude gms dependencies in fdroid build flavour which were pulled in through the jitsi SDK (#3125) Other changes: - Add version details on the login screen, in debug or developer mode + - Migrate Retrofit interface to coroutine calls Changes in Element 1.1.3 (2021-03-18) =================================================== diff --git a/attachment-viewer/build.gradle b/attachment-viewer/build.gradle index 5a8cce92e8..8db57a59af 100644 --- a/attachment-viewer/build.gradle +++ b/attachment-viewer/build.gradle @@ -69,7 +69,7 @@ dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.appcompat:appcompat:1.2.0' - implementation "androidx.recyclerview:recyclerview:1.2.0-rc01" + implementation "androidx.recyclerview:recyclerview:1.2.0" implementation 'com.google.android.material:material:1.3.0' } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 637b1de7cb..b8da6c3864 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { classpath 'com.google.gms:google-services:4.3.5' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1.1' - classpath 'com.google.android.gms:oss-licenses-plugin:0.10.2' + classpath 'com.google.android.gms:oss-licenses-plugin:0.10.3' classpath "com.likethesalad.android:string-reference:1.2.1" // NOTE: Do not place your application dependencies here; they belong diff --git a/fastlane/metadata/android/ar/changelogs/40101010.txt b/fastlane/metadata/android/ar/changelogs/40101010.txt new file mode 100644 index 0000000000..329fffeb3c --- /dev/null +++ b/fastlane/metadata/android/ar/changelogs/40101010.txt @@ -0,0 +1,2 @@ +التغييرات الرئيسة في هذه النسخة: تحسينات على الأداء وإصلاح للعلل! +اطّلع على سجل التغييرات الكامل هنا: https://github.com/vector-im/element-android/releases/tag/v1.1.1 diff --git a/fastlane/metadata/android/ar/title.txt b/fastlane/metadata/android/ar/title.txt index 9b382729c8..11992d355d 100644 --- a/fastlane/metadata/android/ar/title.txt +++ b/fastlane/metadata/android/ar/title.txt @@ -1 +1 @@ -Element (سابقاً Riot.im) +‏Element (‏Riot.im سابقًا) diff --git a/fastlane/metadata/android/ca/changelogs/40101010.txt b/fastlane/metadata/android/ca/changelogs/40101010.txt new file mode 100644 index 0000000000..26ce0562d0 --- /dev/null +++ b/fastlane/metadata/android/ca/changelogs/40101010.txt @@ -0,0 +1,2 @@ +Canvis principals d'aquesta versió: millora de rendiment i correcció d'errors! +Registre de canvis complet: https://github.com/vector-im/element-android/releases/tag/v1.1.1 diff --git a/fastlane/metadata/android/cs/changelogs/40101010.txt b/fastlane/metadata/android/cs/changelogs/40101010.txt new file mode 100644 index 0000000000..73c691da06 --- /dev/null +++ b/fastlane/metadata/android/cs/changelogs/40101010.txt @@ -0,0 +1,2 @@ +Hlavní změny v této verzi: vylepšení výkonnosti a opravy chyb! +Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.1 diff --git a/fastlane/metadata/android/de/changelogs/40100110.txt b/fastlane/metadata/android/de/changelogs/40100110.txt index e70007b5d7..24bc6e518c 100644 --- a/fastlane/metadata/android/de/changelogs/40100110.txt +++ b/fastlane/metadata/android/de/changelogs/40100110.txt @@ -1,2 +1,2 @@ -Diese neue Version enthält hauptsächlich Verbesserungen der Benutzer*innenoberfläche und der Handhabung. Du kannst jetzt ganz schnell Freund*innen einladen und DMs erstellen, indem du schlicht einen QR-Code scannst. +Diese neue Version enthält hauptsächlich Verbesserungen der Benutzeroberfläche und der Handhabung. Du kannst jetzt ganz schnell Freund*innen einladen und DMs erstellen, indem du schlicht einen QR-Code scannst. Vollständige Versionshinweise: https://github.com/vector-im/element-android/releases/tag/v1.0.11 diff --git a/fastlane/metadata/android/de/changelogs/40101010.txt b/fastlane/metadata/android/de/changelogs/40101010.txt new file mode 100644 index 0000000000..59758edcc9 --- /dev/null +++ b/fastlane/metadata/android/de/changelogs/40101010.txt @@ -0,0 +1,2 @@ +Hauptänderungen in dieser Version: Leistungsverbesserungen und Fehlerbehebungen! +Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.1.1 diff --git a/fastlane/metadata/android/de/full_description.txt b/fastlane/metadata/android/de/full_description.txt index 133f5e10d4..568ae61875 100644 --- a/fastlane/metadata/android/de/full_description.txt +++ b/fastlane/metadata/android/de/full_description.txt @@ -3,7 +3,7 @@ Element ist eine neuartige Messaging- und Kollaborationsapp: 1. Volle Kontrolle über deine Privatssphäre 2. Kommuniziere mit jedem aus dem Matrix-Netzwerk und mit der Integration von z.B. Slack sogar über Matrix hinaus 3. Schutz vor Werbung, Datamining und geschlossenen Platformen -4. Absicherung durch Ende-zu-Ende-Verschlüsselung, und Cross Signing um andere zu verifizieren +4. Absicherung durch Ende-zu-Ende-Verschlüsselung, und Cross-Signing um andere zu verifizieren Element unterscheidet sich durch Dezentralität und Open Source deutlich von anderen Messaging- und Kollaborationsapps. @@ -11,11 +11,11 @@ Element ermöglicht es einen eigenen Server zu betreiben - oder einen beliebigen Element ist zu all diesem in der Lage, weil es Matrix nutzt - einen Standard für offene, dezentrale Kommunikation. -Element gibt dir die Kontrolle, indem es dir die Wahl darüber lässt, wer deine Konversationen hostet. In der Element App kannst du zwischen verschiedenen Möglichkeiten auswählen: +Element gibt dir die Kontrolle, indem es dir die Wahl darüber lässt, wer deine Konversationen hostet. In der Element-App kannst du zwischen verschiedenen Möglichkeiten auswählen: 1. Kostenlos auf dem öffentlichen matrix.org Server registrieren, der von den Matrix-Entwicklern gehostet wird, oder wähle aus Tausenden von öffentlichen Servern, die von Freiwilligen gehostet werden -2. Einen Account auf einem eigenen Server auf eigener Hardware betreiben -3. Einen Account auf einem benutzerdefinierten Server erstellen, zum Beispiel durch ein Abonnment bei der Element Matrix Services Hosting-Platform +2. Einen Konto auf einem eigenen Server auf eigener Hardware betreiben +3. Einen Konto auf einem benutzerdefinierten Server erstellen, zum Beispiel durch ein Abonnement bei Element Matrix Services (kurz EMS) Wieso Element nutzen? @@ -23,8 +23,8 @@ Element gibt dir die Kontrolle, indem es dir die Wahl darüber lässt, wer deine OFFENE KOMMUNIKATION UND KOLLABORATION: Du kannst mit jedem im Matrix-Netzwerk schreiben, ob sie nun Element oder eine andere Matrix-App nutzen, oder gar ein anderes Kommunikationssystem wie z.B. Slack, IRC oder XMPP. -SUPER SICHER: Echte Ende-zu-Ende-Verschlüsselung (nur Personen in der Konversation können die Nachrichten entschlüsseln), und Cross Signing um die Geräte der anderen Personen zu verifizieren. +SUPER SICHER: Echte Ende-zu-Ende-Verschlüsselung (nur Personen in der Konversation können die Nachrichten entschlüsseln), und Cross-Signing um die Geräte der anderen Personen zu verifizieren. VOLLSTÄNDIGE KOMMUNIKATION: Nachrichten, Telefonate und Videoanrufe, Teilen von Dateien oder dem eigenen Bildschirm und viele andere Integrationen, Bots und Widgets. Erstelle Räume, Communities, bleib in Kontakt und sei produktiv. -ÜBERALL WO DU BIST: Bleib in Kontakt wo auch immer du bist - mit einem vollständig synchronisierten Nachrichtenverlauf über alle Geräte und im Web auf https://app.element.io. +ÜBERALL WO DU BIST: Bleib in Kontakt wo auch immer du bist - mit einem vollständig synchronisierten Nachrichtenverlauf über alle Geräte und im Netz auf https://app.element.io. diff --git a/fastlane/metadata/android/de/short_description.txt b/fastlane/metadata/android/de/short_description.txt index 0ffacfd8d9..d2c30d4167 100644 --- a/fastlane/metadata/android/de/short_description.txt +++ b/fastlane/metadata/android/de/short_description.txt @@ -1 +1 @@ -Sicherer dezentraler Chat & Telefonie. Schütze deine Daten vor Dritten. +Sicherer dezentraler Chat und Telefonie. Schütze deine Daten vor Dritten. diff --git a/fastlane/metadata/android/en-US/changelogs/40101040.txt b/fastlane/metadata/android/en-US/changelogs/40101040.txt new file mode 100644 index 0000000000..e8977f3211 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40101040.txt @@ -0,0 +1,2 @@ +Main changes in this version: performance improvement and bug fixes! +Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.4 \ No newline at end of file diff --git a/fastlane/metadata/android/et/changelogs/40101010.txt b/fastlane/metadata/android/et/changelogs/40101010.txt new file mode 100644 index 0000000000..4db2c52cb0 --- /dev/null +++ b/fastlane/metadata/android/et/changelogs/40101010.txt @@ -0,0 +1,2 @@ +Põhilised muutused selles versioonis: jõudluse parandused ja pisikohendused. +Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.1.1 diff --git a/fastlane/metadata/android/fa/changelogs/40100120.txt b/fastlane/metadata/android/fa/changelogs/40100120.txt new file mode 100644 index 0000000000..511cdb49fa --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40100120.txt @@ -0,0 +1,2 @@ +تغییرات اصلی در این نگارش: پیش‌نمایش نشانی، صفحه‌کلید اموجی جدید، تنظیم‌های اتاق جدید و برف برای کریسمس! +گزارش تغییر کامل: https://github.com/vector-im/element-android/releases/tag/v1.0.12 diff --git a/fastlane/metadata/android/fa/changelogs/40100130.txt b/fastlane/metadata/android/fa/changelogs/40100130.txt new file mode 100644 index 0000000000..d78c76e041 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40100130.txt @@ -0,0 +1,2 @@ +تغییرات اصلی در این نگارش: پیش‌نمایش نشانی، صفحه‌کلید اموجی جدید، تنظیم‌های اتاق جدید و برف برای کریسمس! +گزارش تغییر کامل: https://github.com/vector-im/element-android/releases/tag/v1.0.13 diff --git a/fastlane/metadata/android/fa/changelogs/40100140.txt b/fastlane/metadata/android/fa/changelogs/40100140.txt new file mode 100644 index 0000000000..5defa284aa --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40100140.txt @@ -0,0 +1,2 @@ +تغییرات اصلی در این نگارش: ویرایش اجازه‌های اتاق، زمینهٔ تاریک/روشن خودکار و رفع دسته‌ای از مشکل‌ها. +گزارش تغییر کامل: https://github.com/vector-im/element-android/releases/tag/v1.0.14 diff --git a/fastlane/metadata/android/fa/changelogs/40100150.txt b/fastlane/metadata/android/fa/changelogs/40100150.txt new file mode 100644 index 0000000000..d856b3a252 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40100150.txt @@ -0,0 +1,2 @@ +تغییرات اصلی در این نگارش: پشتیبانی از ورود اجتماعی. +گزارش تغییر کامل: https://github.com/vector-im/element-android/releases/tag/v1.0.15 diff --git a/fastlane/metadata/android/fa/changelogs/40100160.txt b/fastlane/metadata/android/fa/changelogs/40100160.txt new file mode 100644 index 0000000000..4d8aea0cb6 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40100160.txt @@ -0,0 +1,2 @@ +تغییرات اصلی در این نگارش: پشتیبانی از ورود اجتماعی. +گزارش تغییر کامل: https://github.com/vector-im/element-android/releases/tag/v1.0.15 و https://github.com/vector-im/element-android/releases/tag/v1.0.16 diff --git a/fastlane/metadata/android/fa/changelogs/40100170.txt b/fastlane/metadata/android/fa/changelogs/40100170.txt new file mode 100644 index 0000000000..6de164e57f --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40100170.txt @@ -0,0 +1,2 @@ +تغییرات اصلی در این نگارش: رفع مشکل‌ها! +گزارش تغییر کامل: https://github.com/vector-im/element-android/releases/tag/v1.0.17 diff --git a/fastlane/metadata/android/fa/changelogs/40101000.txt b/fastlane/metadata/android/fa/changelogs/40101000.txt new file mode 100644 index 0000000000..6a3c154ae4 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40101000.txt @@ -0,0 +1,2 @@ +تغییرات اصلی در این نگارش: بهبود ویپ (تماس‌های صوتی و تصویری در پیام‌های مستقیم) و رفع مشکل‌ها! +گزارش تغییر کامل: https://github.com/vector-im/element-android/releases/tag/v1.1.0 diff --git a/fastlane/metadata/android/fa/changelogs/40101010.txt b/fastlane/metadata/android/fa/changelogs/40101010.txt new file mode 100644 index 0000000000..8e29373452 --- /dev/null +++ b/fastlane/metadata/android/fa/changelogs/40101010.txt @@ -0,0 +1,2 @@ +تغییرات اصلی در این نگارش: بهبود عملکرد و رفع مشکل‌ها! +گزارش تغییر کامل: https://github.com/vector-im/element-android/releases/tag/v1.1.1 diff --git a/fastlane/metadata/android/fi/changelogs/40101000.txt b/fastlane/metadata/android/fi/changelogs/40101000.txt new file mode 100644 index 0000000000..1b85b6d00d --- /dev/null +++ b/fastlane/metadata/android/fi/changelogs/40101000.txt @@ -0,0 +1,2 @@ +Suurimmat muutokset tässä versiossa: VoIP-parannuksia ja korjauksia (ääni- ja videopuhelut yksityiskeskusteluissa) +Täysi muutosloki: https://github.com/vector-im/element-android/releases/tag/v1.1.0 diff --git a/fastlane/metadata/android/fi/changelogs/40101010.txt b/fastlane/metadata/android/fi/changelogs/40101010.txt new file mode 100644 index 0000000000..c79023c148 --- /dev/null +++ b/fastlane/metadata/android/fi/changelogs/40101010.txt @@ -0,0 +1,2 @@ +Suurimmat muutokset tässä versiossa: suorituskykyparannuksia ja bugikorjauksia! +Täysi muutosloki: https://github.com/vector-im/element-android/releases/tag/v1.1.1 diff --git a/fastlane/metadata/android/fr/changelogs/40100130.txt b/fastlane/metadata/android/fr/changelogs/40100130.txt index 412b2b9db2..a7e233616a 100644 --- a/fastlane/metadata/android/fr/changelogs/40100130.txt +++ b/fastlane/metadata/android/fr/changelogs/40100130.txt @@ -1,2 +1,2 @@ Principaux changements apportés par cette version : aperçu des URL, nouveau clavier Emoji, nouvelles options de configuration pour le salon et neige pour Noël. -Liste complète des changements : https://github.com/vector-im/element-android/releases/tag/v1.0.12 +Liste complète des changements : https://github.com/vector-im/element-android/releases/tag/v1.0.13 diff --git a/fastlane/metadata/android/fr/changelogs/40100140.txt b/fastlane/metadata/android/fr/changelogs/40100140.txt new file mode 100644 index 0000000000..e823d7a89a --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/40100140.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : modification des permissions dans les salons, thème lumineux/sombre automatique, et plein de corrections de bugs. +Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.0.14 diff --git a/fastlane/metadata/android/fr/changelogs/40100150.txt b/fastlane/metadata/android/fr/changelogs/40100150.txt new file mode 100644 index 0000000000..cfc92299d4 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/40100150.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : prise en charge de l’authentification avec les réseaux sociaux. +Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.0.15 diff --git a/fastlane/metadata/android/fr/changelogs/40100160.txt b/fastlane/metadata/android/fr/changelogs/40100160.txt new file mode 100644 index 0000000000..b5bca83268 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/40100160.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : prise en charge de l’authentification avec les réseaux sociaux ! +Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.0.15 et https://github.com/vector-im/element-android/releases/tag/v1.0.16 diff --git a/fastlane/metadata/android/fr/changelogs/40100170.txt b/fastlane/metadata/android/fr/changelogs/40100170.txt new file mode 100644 index 0000000000..5474f15417 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/40100170.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : corrections de bugs ! +Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1..017 diff --git a/fastlane/metadata/android/fr/changelogs/40101000.txt b/fastlane/metadata/android/fr/changelogs/40101000.txt new file mode 100644 index 0000000000..e9330611ee --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/40101000.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : améliorations de la VoIP (appels audio et vidéo dans les conversations primées) et corrections de bugs ! +Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.0 diff --git a/fastlane/metadata/android/fr/changelogs/40101010.txt b/fastlane/metadata/android/fr/changelogs/40101010.txt new file mode 100644 index 0000000000..8e9de64423 --- /dev/null +++ b/fastlane/metadata/android/fr/changelogs/40101010.txt @@ -0,0 +1,2 @@ +Principaux changements pour cette version : amélioration des performances et corrections de bugs ! +Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.1 diff --git a/fastlane/metadata/android/ga/title.txt b/fastlane/metadata/android/ga/title.txt new file mode 100644 index 0000000000..85dd3fa07f --- /dev/null +++ b/fastlane/metadata/android/ga/title.txt @@ -0,0 +1 @@ +Element (Riot.im roimhe sin) diff --git a/fastlane/metadata/android/it/changelogs/40101000.txt b/fastlane/metadata/android/it/changelogs/40101000.txt new file mode 100644 index 0000000000..bd13c2c185 --- /dev/null +++ b/fastlane/metadata/android/it/changelogs/40101000.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: migliorato il VoIP (chiamate audio e video in MD) e correzione di errori! +Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.1.0 diff --git a/fastlane/metadata/android/it/changelogs/40101010.txt b/fastlane/metadata/android/it/changelogs/40101010.txt new file mode 100644 index 0000000000..51e6659827 --- /dev/null +++ b/fastlane/metadata/android/it/changelogs/40101010.txt @@ -0,0 +1,2 @@ +Modifiche principali in questa versione: prestazioni migliorate e correzione di errori! +Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.1.1 diff --git a/fastlane/metadata/android/kab/short_description.txt b/fastlane/metadata/android/kab/short_description.txt index bee72ea427..453d31fc2a 100644 --- a/fastlane/metadata/android/kab/short_description.txt +++ b/fastlane/metadata/android/kab/short_description.txt @@ -1 +1 @@ -Adiwenni aɣellsan ur nelli aslammas & VoIP. Ḥrez isefra-k•m seg tama tis tlata. +Adiwenni aɣellsan ur nelli d aslammas & VoIP. Ḥrez isefra-k•m seg wis tlata. diff --git a/fastlane/metadata/android/pt-BR/changelogs/40101000.txt b/fastlane/metadata/android/pt-BR/changelogs/40101000.txt new file mode 100644 index 0000000000..8138e376c6 --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40101000.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: Melhoria de VoIP (chamadas de áudio e vídeo em conversas) e correção de erros! +Registro de alterações completo: https://github.com/vector-im/element-android/releases/tag/v1.1.0 diff --git a/fastlane/metadata/android/pt-BR/changelogs/40101010.txt b/fastlane/metadata/android/pt-BR/changelogs/40101010.txt new file mode 100644 index 0000000000..56f9c2955d --- /dev/null +++ b/fastlane/metadata/android/pt-BR/changelogs/40101010.txt @@ -0,0 +1,2 @@ +Principais mudanças nesta versão: melhoria de desempenho e correção de erros! +Registro de alterações completo: https://github.com/vector-im/element-android/releases/tag/v1.1.1 diff --git a/fastlane/metadata/android/ru/changelogs/40101000.txt b/fastlane/metadata/android/ru/changelogs/40101000.txt new file mode 100644 index 0000000000..8ec344a85a --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/40101000.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: VoIP (аудио и видео звонки в ЛС) Улучшение и исправления ошибок! +Полный список изменений: https://github.com/vector-im/element-android/release/tag/v1.1.0 diff --git a/fastlane/metadata/android/ru/changelogs/40101010.txt b/fastlane/metadata/android/ru/changelogs/40101010.txt new file mode 100644 index 0000000000..7295e0df60 --- /dev/null +++ b/fastlane/metadata/android/ru/changelogs/40101010.txt @@ -0,0 +1,2 @@ +Основные изменения в этой версии: улучшение производительности и исправления ошибок! +Полный список изменений: https://github.com/vector-im/element-android/release/tag/v1.1.1 diff --git a/fastlane/metadata/android/sv/changelogs/40101010.txt b/fastlane/metadata/android/sv/changelogs/40101010.txt new file mode 100644 index 0000000000..66a3751aac --- /dev/null +++ b/fastlane/metadata/android/sv/changelogs/40101010.txt @@ -0,0 +1,2 @@ +Huvudsakliga ändringar i den här versionen: Förbättringar och buggfixar! +Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.1 diff --git a/fastlane/metadata/android/tr/changelogs/40100140.txt b/fastlane/metadata/android/tr/changelogs/40100140.txt new file mode 100644 index 0000000000..9a5cf6d5f0 --- /dev/null +++ b/fastlane/metadata/android/tr/changelogs/40100140.txt @@ -0,0 +1,2 @@ +Bu sürümdeki başlıca değişiklikler: Oda izinlerini düzenleme, otomatik koyu/açık tema ve bir avuç hata düzeltmeleri. +Değişim günlüğünün tamamı: https://github.com/vector-im/element-android/releases/tag/v1.0.14 diff --git a/fastlane/metadata/android/tr/changelogs/40100170.txt b/fastlane/metadata/android/tr/changelogs/40100170.txt new file mode 100644 index 0000000000..a93cbb4908 --- /dev/null +++ b/fastlane/metadata/android/tr/changelogs/40100170.txt @@ -0,0 +1,2 @@ +Bu sürümdeki başlıca değişiklikler: Hata düzeltmeleri! +değişim günlüğünün tamamı: https://github.com/vector-im/element-android/releases/tag/v1.0.17 diff --git a/fastlane/metadata/android/tr/changelogs/40101000.txt b/fastlane/metadata/android/tr/changelogs/40101000.txt new file mode 100644 index 0000000000..ce457ee0f4 --- /dev/null +++ b/fastlane/metadata/android/tr/changelogs/40101000.txt @@ -0,0 +1,2 @@ +Bu sürümdeki ana değişiklikler: VoIP (DM'de sesli ve görüntülü aramalar) geliştirmeleri ve hata düzeltmeleri! +Değişim günlüğünün tamamı: https://github.com/vector-im/element-android/releases/tag/v1.1.0 diff --git a/fastlane/metadata/android/tr/changelogs/40101010.txt b/fastlane/metadata/android/tr/changelogs/40101010.txt new file mode 100644 index 0000000000..912d5bcd7c --- /dev/null +++ b/fastlane/metadata/android/tr/changelogs/40101010.txt @@ -0,0 +1,2 @@ +Bu sürümdeki ana değişiklikler: performans iyileştirme ve hata düzeltmeleri! +Değişim günlüğünün tamamı: https://github.com/vector-im/element-android/releases/tag/v1.1.1 diff --git a/fastlane/metadata/android/uk/changelogs/40101010.txt b/fastlane/metadata/android/uk/changelogs/40101010.txt new file mode 100644 index 0000000000..085ac5a118 --- /dev/null +++ b/fastlane/metadata/android/uk/changelogs/40101010.txt @@ -0,0 +1,2 @@ +Основні зміни в цій версії: поліпшення продуктивності та виправлення помилок! +Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.1.1 diff --git a/fastlane/metadata/android/zh-Hans/changelogs/40100100.txt b/fastlane/metadata/android/zh-Hans/changelogs/40100100.txt index 0dc493cf40..0c226c1c8f 100644 --- a/fastlane/metadata/android/zh-Hans/changelogs/40100100.txt +++ b/fastlane/metadata/android/zh-Hans/changelogs/40100100.txt @@ -1,2 +1,2 @@ -此新版本主要包含错误修复和改进。现在,发送消息要快得多。 +此新版本主要包含错误修复和改进。现在,发送消息比以前快多了。 完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.0.10 diff --git a/fastlane/metadata/android/zh-Hans/changelogs/40100120.txt b/fastlane/metadata/android/zh-Hans/changelogs/40100120.txt new file mode 100644 index 0000000000..67d69a3834 --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/40100120.txt @@ -0,0 +1,2 @@ +此版本的主要变化:链接预览,全新 Emoji 键盘,全新聊天室设置功能,以及圣诞节雪花! +完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.0.12 diff --git a/fastlane/metadata/android/zh-Hans/changelogs/40100130.txt b/fastlane/metadata/android/zh-Hans/changelogs/40100130.txt new file mode 100644 index 0000000000..5a2ba4256f --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/40100130.txt @@ -0,0 +1,2 @@ +此版本的主要变化:链接预览,全新 Emoji 键盘,全新聊天室设置功能,以及圣诞节雪花! +完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.0.13 diff --git a/fastlane/metadata/android/zh-Hans/changelogs/40100140.txt b/fastlane/metadata/android/zh-Hans/changelogs/40100140.txt new file mode 100644 index 0000000000..dc25b5094b --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/40100140.txt @@ -0,0 +1,2 @@ +此版本的主要变化:支持编辑聊天室权限,自动切换浅色/深色主题,修复大量错误。 +完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.0.14 diff --git a/fastlane/metadata/android/zh-Hans/changelogs/40100150.txt b/fastlane/metadata/android/zh-Hans/changelogs/40100150.txt new file mode 100644 index 0000000000..d5f37ff3a6 --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/40100150.txt @@ -0,0 +1,2 @@ +此版本的主要变化:支持通过社交网络登录。 +完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.0.15 diff --git a/fastlane/metadata/android/zh-Hans/changelogs/40100160.txt b/fastlane/metadata/android/zh-Hans/changelogs/40100160.txt new file mode 100644 index 0000000000..c0658e1881 --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/40100160.txt @@ -0,0 +1,2 @@ +此版本的主要变化:支持通过社交网络登录。 +完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.0.15 和 https://github.com/vector-im/element-android/releases/tag/v1.0.16 diff --git a/fastlane/metadata/android/zh-Hans/changelogs/40100170.txt b/fastlane/metadata/android/zh-Hans/changelogs/40100170.txt new file mode 100644 index 0000000000..55cbadb37f --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/40100170.txt @@ -0,0 +1,2 @@ +此版本的主要变化:修复错误! +完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.0.17 diff --git a/fastlane/metadata/android/zh-Hans/changelogs/40101000.txt b/fastlane/metadata/android/zh-Hans/changelogs/40101000.txt new file mode 100644 index 0000000000..95bd9c55c0 --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/40101000.txt @@ -0,0 +1,2 @@ +此版本的主要变化:改进 VoIP(私聊中的音频与视频通话)以及修复错误! +完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.1.0 diff --git a/fastlane/metadata/android/zh-Hans/changelogs/40101010.txt b/fastlane/metadata/android/zh-Hans/changelogs/40101010.txt new file mode 100644 index 0000000000..9a4e611cf9 --- /dev/null +++ b/fastlane/metadata/android/zh-Hans/changelogs/40101010.txt @@ -0,0 +1,2 @@ +此版本的主要变化:改进性能以及修复错误! +完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.1.1 diff --git a/fastlane/metadata/android/zh-Hans/full_description.txt b/fastlane/metadata/android/zh-Hans/full_description.txt index 12664f7c9b..4791c9652b 100644 --- a/fastlane/metadata/android/zh-Hans/full_description.txt +++ b/fastlane/metadata/android/zh-Hans/full_description.txt @@ -1,30 +1,30 @@ -Element 是一种新型消息和协作应用: +Element 是一种新型的通讯与协作应用: 1. 使您可以掌控您的隐私 -2. 使您与 Matrix 网络中的任何人交流,甚至可以通过与其他应用如 Slack 集成 -3. 保护您远离广告,数据挖掘和围墙花园 -4. 通过端到端加密保护您,通过交叉签名验证其他人 +2. 使您与 Matrix 网络中的任何人交流,甚至可以通过集成功能与如 Slack 之类的其他应用通讯 +3. 保护您免受广告,大数据挖掘和封闭服务的侵害 +4. 通过端到端加密保证安全,通过交叉签名验证其他人 -Element 与其他消息和协作应用完全不同,因为它是去中心化且开源的。 +Element 与其他通讯与协作应用完全不同,因为它是去中心化且开源的。 -Element 使您可以自托管 - 或选择托管商 - 因此您拥有您的数据和会话的隐私权,所有权和控制权。它使您可以访问开放网络;因此您可以不仅仅与其他 Element 用户交流。并且它非常安全。 +Element 允许您自托管——或者选择托管商——因此,您能拥有数据和会话的隐私权,所有权和控制权。它允许您访问开放网络;因此,您可以与 Element 用户以外的人交流。并且它非常安全。 -Element 可以做到这些因为它在 Matrix 上运行 - 开放,去中心化通信标准。 +Element 之所以可以做到这些,是因为它在 Matrix 上运行——开放,去中心化通讯的标准。 -Element 通过让您选择谁来托管您的会话使您掌控一切。在 Element 应用中,您可以选择不同的托管方式: +通过让您选择由谁来托管您的会话,Element 让您掌控一切。在 Element 应用中,您可以选择不同的托管方式: -1. 在由 Matrix 开发者托管的 matrix.org 公共服务器上获取免费帐户,或从志愿者托管的几千个公共服务器中选择 -2. 在您自己的硬件上运行服务器自托管您的会话 -3. 通过简单地订阅 Element Matrix Services 托管平台在自定义服务器上注册账户 +1. 在由 Matrix 开发者托管的 matrix.org 公共服务器上获取免费帐户,或从志愿者托管的上千个公共服务器中选择 +2. 在您自己的硬件上运行服务器,自托管您的会话 +3. 通过订阅 Element Matrix Services 托管平台,简单地在自定义服务器上注册账户 为什么选择 Element? -拥有您的数据:您来决定存放您的数据和消息的位置。拥有并控制它的是您,而不是挖掘您的数据或与第三方分享的巨型企业。 +掌控您的数据:您来决定存放您的数据和消息的位置。拥有并控制它的是您,而不是挖掘您的数据或与第三方分享的巨型企业。 -开放消息与协作:您可以与 Matrix 网络中的任何人聊天,不论他们使用 Element 还是其他 Matrix 应用,甚至即使他们在使用不同的消息系统例如 Slack,IRC 或 XMPP。 +开放通讯与协作:您可以与 Matrix 网络中的任何人聊天,不论他们使用 Element 还是其他 Matrix 应用,甚至/即使他们在使用不同的通讯系统,例如 Slack,IRC 或 XMPP。 -超级安全:真正的端到端加密(仅有会话中的人可以解密消息),及用于验证会话参与方的设备的交叉签名。 +超级安全:支持真正的端到端加密(仅有会话中的人可以解密消息),还有能够验证会话参与方的设备的交叉签名。 -丰富的通信方式:消息,语音和视频通话,文件分享,屏幕分享和大量集成,机器人和小部件。建立房间,社区,保持联系并做好工作。 +完善的通讯方式:消息,语音和视频通话,文件共享,屏幕共享和大量集成功能,机器人和小挂件。建立房间与社区,保持联系并完成工作。 -随时随地:通过在您的全部设备和 https://app.element.io 网页上完全同步的消息历史,无论您在哪里都可以保持联系。 +随时随地:消息历史可在您的全部设备和 https://app.element.io 网页端之间完全同步,无论您在哪里,都可以保持联系。 diff --git a/fastlane/metadata/android/zh-Hans/short_description.txt b/fastlane/metadata/android/zh-Hans/short_description.txt index 87d127335b..53d7d33403 100644 --- a/fastlane/metadata/android/zh-Hans/short_description.txt +++ b/fastlane/metadata/android/zh-Hans/short_description.txt @@ -1 +1 @@ -安全去中心化的聊天和 VoIP。保护您的数据不受第三方的影响。 +安全、去中心化的聊天与 VoIP 通话。保护您的数据不被第三方窃取。 diff --git a/fastlane/metadata/android/zh-Hant/changelogs/40101010.txt b/fastlane/metadata/android/zh-Hant/changelogs/40101010.txt new file mode 100644 index 0000000000..8b0e45e6b3 --- /dev/null +++ b/fastlane/metadata/android/zh-Hant/changelogs/40101010.txt @@ -0,0 +1,2 @@ +此版本的主要變更:效能改進與錯誤修復! +完整變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.1.1 diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 0ca605dc8b..669444d563 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -108,11 +108,11 @@ static def gitRevisionDate() { dependencies { def arrow_version = "0.8.2" - def moshi_version = '1.11.0' + def moshi_version = '1.12.0' def lifecycle_version = '2.2.0' def arch_version = '2.1.0' def markwon_version = '3.1.0' - def daggerVersion = '2.33' + def daggerVersion = '2.34' def work_version = '2.5.0' def retrofit_version = '2.9.0' @@ -166,7 +166,7 @@ dependencies { implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.20' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.21' testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.5.1' diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestRoomDisplayNameFallbackProvider.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestRoomDisplayNameFallbackProvider.kt index 7a1d4604f0..af2d57f9ce 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestRoomDisplayNameFallbackProvider.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestRoomDisplayNameFallbackProvider.kt @@ -18,23 +18,26 @@ package org.matrix.android.sdk.common import org.matrix.android.sdk.api.RoomDisplayNameFallbackProvider -class TestRoomDisplayNameFallbackProvider() : RoomDisplayNameFallbackProvider { +class TestRoomDisplayNameFallbackProvider : RoomDisplayNameFallbackProvider { override fun getNameForRoomInvite() = "Room invite" - override fun getNameForEmptyRoom() = + override fun getNameForEmptyRoom(isDirect: Boolean, leftMemberNames: List) = "Empty room" - override fun getNameFor2members(name1: String?, name2: String?) = + override fun getNameFor1member(name: String) = + name + + override fun getNameFor2members(name1: String, name2: String) = "$name1 and $name2" - override fun getNameFor3members(name1: String?, name2: String?, name3: String?) = + override fun getNameFor3members(name1: String, name2: String, name3: String) = "$name1, $name2 and $name3" - override fun getNameFor4members(name1: String?, name2: String?, name3: String?, name4: String?) = + override fun getNameFor4members(name1: String, name2: String, name3: String, name4: String) = "$name1, $name2, $name3 and $name4" - override fun getNameFor4membersAndMore(name1: String?, name2: String?, name3: String?, remainingCount: Int) = + override fun getNameFor4membersAndMore(name1: String, name2: String, name3: String, remainingCount: Int) = "$name1, $name2, $name3 and $remainingCount others" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/RoomDisplayNameFallbackProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/RoomDisplayNameFallbackProvider.kt index 4ac14d5f63..a34dbcc196 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/RoomDisplayNameFallbackProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/RoomDisplayNameFallbackProvider.kt @@ -18,9 +18,10 @@ package org.matrix.android.sdk.api interface RoomDisplayNameFallbackProvider { fun getNameForRoomInvite(): String - fun getNameForEmptyRoom(): String - fun getNameFor2members(name1: String?, name2: String?): String - fun getNameFor3members(name1: String?, name2: String?, name3: String?): String - fun getNameFor4members(name1: String?, name2: String?, name3: String?, name4: String?): String - fun getNameFor4membersAndMore(name1: String?, name2: String?, name3: String?, remainingCount: Int): String + fun getNameForEmptyRoom(isDirect: Boolean, leftMemberNames: List): String + fun getNameFor1member(name: String): String + fun getNameFor2members(name1: String, name2: String): String + fun getNameFor3members(name1: String, name2: String, name3: String): String + fun getNameFor4members(name1: String, name2: String, name3: String, name4: String): String + fun getNameFor4membersAndMore(name1: String, name2: String, name3: String, remainingCount: Int): String } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationAvailability.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationAvailability.kt new file mode 100644 index 0000000000..f9a7ace7ba --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationAvailability.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.auth.registration + +import org.matrix.android.sdk.api.failure.Failure + +sealed class RegistrationAvailability { + object Available : RegistrationAvailability() + data class NotAvailable(val failure: Failure.ServerError) : RegistrationAvailability() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt index d00c9a0c82..38a5a77291 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt @@ -36,6 +36,8 @@ interface RegistrationWizard { suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult + suspend fun registrationAvailable(userName: String): RegistrationAvailability + val currentThreePid: String? // True when login and password has been sent with success to the homeserver diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt index e0ee9f36ba..0ba61e5890 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt @@ -37,6 +37,18 @@ fun Throwable.shouldBeRetried(): Boolean { || (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED) } +/** + * Get the retry delay in case of rate limit exceeded error, adding 100 ms, of defaultValue otherwise + */ +fun Throwable.getRetryDelay(defaultValue: Long): Long { + return (this as? Failure.ServerError) + ?.error + ?.takeIf { it.code == MatrixError.M_LIMIT_EXCEEDED } + ?.retryAfterMillis + ?.plus(100L) + ?: defaultValue +} + fun Throwable.isInvalidPassword(): Boolean { return this is Failure.ServerError && error.code == MatrixError.M_FORBIDDEN @@ -53,13 +65,16 @@ fun Throwable.isInvalidUIAAuth(): Boolean { * Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible */ fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? { - return if (this is Failure.OtherServerError && httpCode == 401) { + return if (this is Failure.OtherServerError + && httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */) { tryOrNull { MoshiProvider.providesMoshi() .adapter(RegistrationFlowResponse::class.java) .fromJson(errorBody) } - } else if (this is Failure.ServerError && httpCode == 401 && error.code == MatrixError.M_FORBIDDEN) { + } else if (this is Failure.ServerError + && httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */ + && error.code == MatrixError.M_FORBIDDEN) { // This happens when the submission for this stage was bad (like bad password) if (error.session != null && error.flows != null) { RegistrationFlowResponse( @@ -75,3 +90,11 @@ fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? { null } } + +fun Throwable.isRegistrationAvailabilityError(): Boolean { + return this is Failure.ServerError + && httpCode == HttpsURLConnection.HTTP_BAD_REQUEST /* 400 */ + && (error.code == MatrixError.M_USER_IN_USE + || error.code == MatrixError.M_INVALID_USERNAME + || error.code == MatrixError.M_EXCLUSIVE) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt index 4da1662681..d9bf5cfd13 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt @@ -39,6 +39,8 @@ interface PushRuleService { fun removePushRuleListener(listener: PushRuleListener) + fun getActions(event: Event): List + // fun fulfilledBingRule(event: Event, rules: List): PushRule? interface PushRuleListener { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomCategoryFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomCategoryFilter.kt new file mode 100644 index 0000000000..c8ccc4c8a3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomCategoryFilter.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.query + +enum class RoomCategoryFilter { + ONLY_DM, + ONLY_ROOMS, + ONLY_WITH_NOTIFICATIONS, + ALL +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomTagQueryFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomTagQueryFilter.kt new file mode 100644 index 0000000000..613916bc18 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomTagQueryFilter.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.query + +data class RoomTagQueryFilter( + val isFavorite: Boolean?, + val isLowPriority: Boolean?, + val isServerNotice: Boolean? +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index 7a24ccac11..a15799d862 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.call.CallSignalingService import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.crypto.CryptoService +import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.group.GroupService @@ -68,6 +69,7 @@ interface Session : SignOutService, FilterService, TermsService, + EventService, ProfileService, PushRuleService, PushersService, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt new file mode 100644 index 0000000000..297f277497 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.events + +import org.matrix.android.sdk.api.session.events.model.Event + +interface EventService { + + /** + * Ask the homeserver for an event content. The SDK will try to decrypt it if it is possible + * The result will not be stored into cache + */ + suspend fun getEvent(roomId: String, + eventId: String): Event +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 844e8dbbab..89b873febb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -289,3 +289,7 @@ fun Event.getRelationContent(): RelationDefaultContent? { fun Event.isReply(): Boolean { return getRelationContent()?.inReplyTo?.eventId != null } + +fun Event.isEdition(): Boolean { + return getRelationContent()?.takeIf { it.type == RelationType.REPLACE }?.eventId != null +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index bc5cb3c8f4..22045366cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -17,12 +17,14 @@ package org.matrix.android.sdk.api.session.room import androidx.lifecycle.LiveData +import androidx.paging.PagedList import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.peeking.PeekResult +import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription @@ -170,4 +172,29 @@ interface RoomService { * This call will try to gather some information on this room, but it could fail and get nothing more */ suspend fun peekRoom(roomIdOrAlias: String): PeekResult + + /** + * TODO Doc + */ + fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config = defaultPagedListConfig): LiveData> + + /** + * TODO Doc + */ + fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config = defaultPagedListConfig): UpdatableFilterLivePageResult + + /** + * TODO Doc + */ + fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount + + private val defaultPagedListConfig + get() = PagedList.Config.Builder() + .setPageSize(10) + .setInitialLoadSizeHint(20) + .setEnablePlaceholders(false) + .setPrefetchDistance(10) + .build() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt index f859d74a6f..7e04ebb5f2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt @@ -17,6 +17,8 @@ package org.matrix.android.sdk.api.session.room import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.RoomCategoryFilter +import org.matrix.android.sdk.api.query.RoomTagQueryFilter import org.matrix.android.sdk.api.session.room.model.Membership fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): RoomSummaryQueryParams { @@ -31,7 +33,9 @@ data class RoomSummaryQueryParams( val roomId: QueryStringValue, val displayName: QueryStringValue, val canonicalAlias: QueryStringValue, - val memberships: List + val memberships: List, + val roomCategoryFilter: RoomCategoryFilter?, + val roomTagQueryFilter: RoomTagQueryFilter? ) { class Builder { @@ -40,12 +44,16 @@ data class RoomSummaryQueryParams( var displayName: QueryStringValue = QueryStringValue.IsNotEmpty var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition var memberships: List = Membership.all() + var roomCategoryFilter: RoomCategoryFilter? = RoomCategoryFilter.ALL + var roomTagQueryFilter: RoomTagQueryFilter? = null fun build() = RoomSummaryQueryParams( roomId = roomId, displayName = displayName, canonicalAlias = canonicalAlias, - memberships = memberships + memberships = memberships, + roomCategoryFilter = roomCategoryFilter, + roomTagQueryFilter = roomTagQueryFilter ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeRoomListDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt similarity index 60% rename from vector/src/main/java/im/vector/app/features/home/HomeRoomListDataSource.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt index 6bcd6f01eb..71b3c665e7 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeRoomListDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt @@ -1,11 +1,11 @@ /* - * Copyright 2019 New Vector Ltd + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,12 +14,14 @@ * limitations under the License. */ -package im.vector.app.features.home +package org.matrix.android.sdk.api.session.room -import im.vector.app.core.utils.BehaviorDataSource +import androidx.lifecycle.LiveData +import androidx.paging.PagedList import org.matrix.android.sdk.api.session.room.model.RoomSummary -import javax.inject.Inject -import javax.inject.Singleton -@Singleton -class HomeRoomListDataSource @Inject constructor() : BehaviorDataSource>() +interface UpdatableFilterLivePageResult { + val livePagedList: LiveData> + + fun updateQuery(queryParams: RoomSummaryQueryParams) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt index a2b4e135d1..c96a800ee5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt @@ -35,5 +35,5 @@ object MessageType { const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker" const val MSGTYPE_CONFETTI = "nic.custom.confetti" - const val MSGTYPE_SNOW = "nic.custom.snow" + const val MSGTYPE_SNOW = "io.element.effect.snowfall" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt new file mode 100644 index 0000000000..066178b1ec --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.summary + +data class RoomAggregateNotificationCount( + val notificationCount: Int, + val highlightCount: Int +) { + val totalCount = notificationCount + highlightCount + val isHighlight = highlightCount > 0 +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt index f92ae7e0ee..f93f285c6e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.auth import org.matrix.android.sdk.api.auth.data.Credentials +import org.matrix.android.sdk.internal.auth.data.Availability import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams import org.matrix.android.sdk.internal.auth.data.RiotConfig @@ -29,12 +30,12 @@ import org.matrix.android.sdk.internal.auth.registration.SuccessResult import org.matrix.android.sdk.internal.auth.registration.ValidationCodeBody import org.matrix.android.sdk.internal.auth.version.Versions import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Headers import retrofit2.http.POST import retrofit2.http.Path +import retrofit2.http.Query import retrofit2.http.Url /** @@ -45,26 +46,32 @@ internal interface AuthAPI { * Get a Riot config file, using the name including the domain */ @GET("config.{domain}.json") - fun getRiotConfigDomain(@Path("domain") domain: String): Call + suspend fun getRiotConfigDomain(@Path("domain") domain: String): RiotConfig /** * Get a Riot config file */ @GET("config.json") - fun getRiotConfig(): Call + suspend fun getRiotConfig(): RiotConfig /** * Get the version information of the homeserver */ @GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions") - fun versions(): Call + suspend fun versions(): Versions /** * Register to the homeserver, or get error 401 with a RegistrationFlowResponse object if registration is incomplete * Ref: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register") - fun register(@Body registrationParams: RegistrationParams): Call + suspend fun register(@Body registrationParams: RegistrationParams): Credentials + + /** + * Checks to see if a username is available, and valid, for the server. + */ + @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register/available") + suspend fun registerAvailable(@Query("username") username: String): Availability /** * Add 3Pid during registration @@ -72,22 +79,22 @@ internal interface AuthAPI { * https://github.com/matrix-org/matrix-doc/pull/2290 */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register/{threePid}/requestToken") - fun add3Pid(@Path("threePid") threePid: String, - @Body params: AddThreePidRegistrationParams): Call + suspend fun add3Pid(@Path("threePid") threePid: String, + @Body params: AddThreePidRegistrationParams): AddThreePidRegistrationResponse /** * Validate 3pid */ @POST - fun validate3Pid(@Url url: String, - @Body params: ValidationCodeBody): Call + suspend fun validate3Pid(@Url url: String, + @Body params: ValidationCodeBody): SuccessResult /** * Get the supported login flow * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-login */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login") - fun getLoginFlows(): Call + suspend fun getLoginFlows(): LoginFlowResponse /** * Pass params to the server for the current login phase. @@ -97,22 +104,22 @@ internal interface AuthAPI { */ @Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login") - fun login(@Body loginParams: PasswordLoginParams): Call + suspend fun login(@Body loginParams: PasswordLoginParams): Credentials // Unfortunately we cannot use interface for @Body parameter, so I duplicate the method for the type TokenLoginParams @Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login") - fun login(@Body loginParams: TokenLoginParams): Call + suspend fun login(@Body loginParams: TokenLoginParams): Credentials /** * Ask the homeserver to reset the password associated with the provided email. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password/email/requestToken") - fun resetPassword(@Body params: AddThreePidRegistrationParams): Call + suspend fun resetPassword(@Body params: AddThreePidRegistrationParams): AddThreePidRegistrationResponse /** * Ask the homeserver to reset the password with the provided new password once the email is validated. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password") - fun resetPasswordMailConfirmed(@Body params: ResetPasswordMailConfirmed): Call + suspend fun resetPasswordMailConfirmed(@Body params: ResetPasswordMailConfirmed) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 4f3451cf30..e26286ad2f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -31,7 +31,6 @@ import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.appendParamToUrl import org.matrix.android.sdk.internal.SessionManager -import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse import org.matrix.android.sdk.internal.auth.data.RiotConfig import org.matrix.android.sdk.internal.auth.db.PendingSessionData import org.matrix.android.sdk.internal.auth.login.DefaultLoginWizard @@ -172,8 +171,8 @@ internal class DefaultAuthenticationService @Inject constructor( // First check the homeserver version return runCatching { - executeRequest(null) { - apiCall = authAPI.versions() + executeRequest(null) { + authAPI.versions() } } .map { versions -> @@ -204,8 +203,8 @@ internal class DefaultAuthenticationService @Inject constructor( // Ok, try to get the config.domain.json file of a RiotWeb client return runCatching { - executeRequest(null) { - apiCall = authAPI.getRiotConfigDomain(domain) + executeRequest(null) { + authAPI.getRiotConfigDomain(domain) } } .map { riotConfig -> @@ -232,8 +231,8 @@ internal class DefaultAuthenticationService @Inject constructor( // Ok, try to get the config.json file of a RiotWeb client return runCatching { - executeRequest(null) { - apiCall = authAPI.getRiotConfig() + executeRequest(null) { + authAPI.getRiotConfig() } } .map { riotConfig -> @@ -265,8 +264,8 @@ internal class DefaultAuthenticationService @Inject constructor( val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) - val versions = executeRequest(null) { - apiCall = newAuthAPI.versions() + val versions = executeRequest(null) { + newAuthAPI.versions() } return getLoginFlowResult(newAuthAPI, versions, defaultHomeServerUrl) @@ -293,8 +292,8 @@ internal class DefaultAuthenticationService @Inject constructor( val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) - val versions = executeRequest(null) { - apiCall = newAuthAPI.versions() + val versions = executeRequest(null) { + newAuthAPI.versions() } getLoginFlowResult(newAuthAPI, versions, wellknownResult.homeServerUrl) @@ -305,8 +304,8 @@ internal class DefaultAuthenticationService @Inject constructor( private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult { // Get the login flow - val loginFlowResponse = executeRequest(null) { - apiCall = authAPI.getLoginFlows() + val loginFlowResponse = executeRequest(null) { + authAPI.getLoginFlows() } return LoginFlowResult.Success( loginFlowResponse.flows.orEmpty().mapNotNull { it.type }, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt index b8416d69bf..867cf46b8d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt @@ -20,7 +20,6 @@ import dagger.Lazy import okhttp3.OkHttpClient import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse import org.matrix.android.sdk.internal.di.Unauthenticated import org.matrix.android.sdk.internal.network.RetrofitFactory import org.matrix.android.sdk.internal.network.executeRequest @@ -49,8 +48,8 @@ internal class DefaultIsValidClientServerApiTask @Inject constructor( .create(AuthAPI::class.java) return try { - executeRequest(null) { - apiCall = authAPI.getLoginFlows() + executeRequest(null) { + authAPI.getLoginFlows() } // We get a response, so the API is valid true diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/Availability.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/Availability.kt new file mode 100644 index 0000000000..5ef3c0d06a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/Availability.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.auth.data + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class Availability( + /** + * A flag to indicate that the username is available. This should always be true when the server replies with 200 OK. + */ + @Json(name = "available") + val available: Boolean? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt index 4167875849..8b81f42e03 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.auth.login import android.util.Patterns -import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.registration.RegisterThreePid import org.matrix.android.sdk.api.session.Session @@ -29,7 +28,6 @@ import org.matrix.android.sdk.internal.auth.data.ThreePidMedium import org.matrix.android.sdk.internal.auth.data.TokenLoginParams import org.matrix.android.sdk.internal.auth.db.PendingSessionData import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams -import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse import org.matrix.android.sdk.internal.auth.registration.RegisterAddThreePidTask import org.matrix.android.sdk.internal.network.executeRequest @@ -49,8 +47,8 @@ internal class DefaultLoginWizard( } else { PasswordLoginParams.userIdentifier(login, password, deviceName) } - val credentials = executeRequest(null) { - apiCall = authAPI.login(loginParams) + val credentials = executeRequest(null) { + authAPI.login(loginParams) } return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) @@ -63,8 +61,8 @@ internal class DefaultLoginWizard( val loginParams = TokenLoginParams( token = loginToken ) - val credentials = executeRequest(null) { - apiCall = authAPI.login(loginParams) + val credentials = executeRequest(null) { + authAPI.login(loginParams) } return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) @@ -80,8 +78,8 @@ internal class DefaultLoginWizard( pendingSessionData = pendingSessionData.copy(sendAttempt = pendingSessionData.sendAttempt + 1) .also { pendingSessionStore.savePendingSessionData(it) } - val result = executeRequest(null) { - apiCall = authAPI.resetPassword(AddThreePidRegistrationParams.from(param)) + val result = executeRequest(null) { + authAPI.resetPassword(AddThreePidRegistrationParams.from(param)) } pendingSessionData = pendingSessionData.copy(resetPasswordData = ResetPasswordData(newPassword, result)) @@ -98,8 +96,8 @@ internal class DefaultLoginWizard( safeResetPasswordData.newPassword ) - executeRequest(null) { - apiCall = authAPI.resetPasswordMailConfirmed(param) + executeRequest(null) { + authAPI.resetPasswordMailConfirmed(param) } // Set to null? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt index be6ff38931..77bbb8096f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.auth.login import dagger.Lazy -import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session @@ -59,19 +58,16 @@ internal class DefaultDirectLoginTask @Inject constructor( val loginParams = PasswordLoginParams.userIdentifier(params.userId, params.password, params.deviceName) val credentials = try { - executeRequest(null) { - apiCall = authAPI.login(loginParams) + executeRequest(null) { + authAPI.login(loginParams) } } catch (throwable: Throwable) { - when (throwable) { - is UnrecognizedCertificateException -> { - throw Failure.UnrecognizedCertificateFailure( - homeServerUrl, - throwable.fingerprint - ) - } - else -> - throw throwable + throw when (throwable) { + is UnrecognizedCertificateException -> Failure.UnrecognizedCertificateFailure( + homeServerUrl, + throwable.fingerprint + ) + else -> throwable } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt index 91e414e689..4a3d53a8fc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.auth.registration import kotlinx.coroutines.delay import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegisterThreePid +import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability import org.matrix.android.sdk.api.auth.registration.RegistrationResult import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.registration.toFlowResult @@ -40,9 +41,10 @@ internal class DefaultRegistrationWizard( private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here") - private val registerTask = DefaultRegisterTask(authAPI) - private val registerAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI) - private val validateCodeTask = DefaultValidateCodeTask(authAPI) + private val registerTask: RegisterTask = DefaultRegisterTask(authAPI) + private val registerAvailableTask: RegisterAvailableTask = DefaultRegisterAvailableTask(authAPI) + private val registerAddThreePidTask: RegisterAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI) + private val validateCodeTask: ValidateCodeTask = DefaultValidateCodeTask(authAPI) override val currentThreePid: String? get() { @@ -203,4 +205,8 @@ internal class DefaultRegistrationWizard( val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) return RegistrationResult.Success(session) } + + override suspend fun registrationAvailable(userName: String): RegistrationAvailability { + return registerAvailableTask.execute(RegisterAvailableTask.Params(userName)) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAddThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAddThreePidTask.kt index 57c4b72b8a..54a8ba0e6c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAddThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAddThreePidTask.kt @@ -35,7 +35,7 @@ internal class DefaultRegisterAddThreePidTask( override suspend fun execute(params: RegisterAddThreePidTask.Params): AddThreePidRegistrationResponse { return executeRequest(null) { - apiCall = authAPI.add3Pid(params.threePid.toPath(), AddThreePidRegistrationParams.from(params)) + authAPI.add3Pid(params.threePid.toPath(), AddThreePidRegistrationParams.from(params)) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAvailableTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAvailableTask.kt new file mode 100644 index 0000000000..314a24dad4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAvailableTask.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.auth.registration + +import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.failure.isRegistrationAvailabilityError +import org.matrix.android.sdk.internal.auth.AuthAPI +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.task.Task + +internal interface RegisterAvailableTask : Task { + data class Params( + val userName: String + ) +} + +internal class DefaultRegisterAvailableTask(private val authAPI: AuthAPI) : RegisterAvailableTask { + override suspend fun execute(params: RegisterAvailableTask.Params): RegistrationAvailability { + return try { + executeRequest(null) { + authAPI.registerAvailable(params.userName) + } + RegistrationAvailability.Available + } catch (exception: Throwable) { + if (exception.isRegistrationAvailabilityError()) { + RegistrationAvailability.NotAvailable(exception as Failure.ServerError) + } else { + throw exception + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterTask.kt index bf5d899276..45668cb8ad 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterTask.kt @@ -36,7 +36,7 @@ internal class DefaultRegisterTask( override suspend fun execute(params: RegisterTask.Params): Credentials { try { return executeRequest(null) { - apiCall = authAPI.register(params.registrationParams) + authAPI.register(params.registrationParams) } } catch (throwable: Throwable) { throw throwable.toRegistrationFlowResponse() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidateCodeTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidateCodeTask.kt index b297c9849d..d68b7cd9eb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidateCodeTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidateCodeTask.kt @@ -33,7 +33,7 @@ internal class DefaultValidateCodeTask( override suspend fun execute(params: ValidateCodeTask.Params): SuccessResult { return executeRequest(null) { - apiCall = authAPI.validate3Pid(params.url, params.body) + authAPI.validate3Pid(params.url, params.body) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt index 5604e97152..cef86e8b5e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt @@ -30,7 +30,6 @@ import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse import org.matrix.android.sdk.internal.crypto.model.rest.UpdateDeviceInfoBody import org.matrix.android.sdk.internal.crypto.model.rest.UploadSigningKeysBody import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.HTTP @@ -46,14 +45,14 @@ internal interface CryptoApi { * Doc: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-devices */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices") - fun getDevices(): Call + suspend fun getDevices(): DevicesListResponse /** * Get the device info by id * Doc: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-devices-deviceid */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{deviceId}") - fun getDeviceInfo(@Path("deviceId") deviceId: String): Call + suspend fun getDeviceInfo(@Path("deviceId") deviceId: String): DeviceInfo /** * Upload device and/or one-time keys. @@ -62,7 +61,7 @@ internal interface CryptoApi { * @param body the keys to be sent. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/upload") - fun uploadKeys(@Body body: KeysUploadBody): Call + suspend fun uploadKeys(@Body body: KeysUploadBody): KeysUploadResponse /** * Download device keys. @@ -71,7 +70,7 @@ internal interface CryptoApi { * @param params the params. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/query") - fun downloadKeysForUsers(@Body params: KeysQueryBody): Call + suspend fun downloadKeysForUsers(@Body params: KeysQueryBody): KeysQueryResponse /** * CrossSigning - Uploading signing keys @@ -79,7 +78,7 @@ internal interface CryptoApi { * This endpoint requires UI Auth. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "keys/device_signing/upload") - fun uploadSigningKeys(@Body params: UploadSigningKeysBody): Call + suspend fun uploadSigningKeys(@Body params: UploadSigningKeysBody): KeysQueryResponse /** * CrossSigning - Uploading signatures @@ -98,7 +97,7 @@ internal interface CryptoApi { * However, signatures made for other users' keys, made by her user-signing key, will not be included. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "keys/signatures/upload") - fun uploadSignatures(@Body params: Map?): Call + suspend fun uploadSignatures(@Body params: Map?): SignatureUploadResponse /** * Claim one-time keys. @@ -107,7 +106,7 @@ internal interface CryptoApi { * @param params the params. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/claim") - fun claimOneTimeKeysForUsersDevices(@Body body: KeysClaimBody): Call + suspend fun claimOneTimeKeysForUsersDevices(@Body body: KeysClaimBody): KeysClaimResponse /** * Send an event to a specific list of devices @@ -118,9 +117,9 @@ internal interface CryptoApi { * @param body the body */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sendToDevice/{eventType}/{txnId}") - fun sendToDevice(@Path("eventType") eventType: String, - @Path("txnId") transactionId: String, - @Body body: SendToDeviceBody): Call + suspend fun sendToDevice(@Path("eventType") eventType: String, + @Path("txnId") transactionId: String, + @Body body: SendToDeviceBody) /** * Delete a device. @@ -130,8 +129,8 @@ internal interface CryptoApi { * @param params the deletion parameters */ @HTTP(path = NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{device_id}", method = "DELETE", hasBody = true) - fun deleteDevice(@Path("device_id") deviceId: String, - @Body params: DeleteDeviceParams): Call + suspend fun deleteDevice(@Path("device_id") deviceId: String, + @Body params: DeleteDeviceParams) /** * Update the device information. @@ -141,8 +140,8 @@ internal interface CryptoApi { * @param params the params */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{device_id}") - fun updateDeviceInfo(@Path("device_id") deviceId: String, - @Body params: UpdateDeviceInfoBody): Call + suspend fun updateDeviceInfo(@Path("device_id") deviceId: String, + @Body params: UpdateDeviceInfoBody) /** * Get the update devices list from two sync token. @@ -152,6 +151,6 @@ internal interface CryptoApi { * @param newToken the up-to token. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/changes") - fun getKeyChanges(@Query("from") oldToken: String, - @Query("to") newToken: String): Call + suspend fun getKeyChanges(@Query("from") oldToken: String, + @Query("to") newToken: String): KeyChangesResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt index 3f8333528f..eb4c55a3e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt @@ -25,7 +25,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionR import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.RoomKeysBackupData import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET @@ -48,14 +47,14 @@ internal interface RoomKeysApi { * @param createKeysBackupVersionBody the body */ @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version") - fun createKeysBackupVersion(@Body createKeysBackupVersionBody: CreateKeysBackupVersionBody): Call + suspend fun createKeysBackupVersion(@Body createKeysBackupVersionBody: CreateKeysBackupVersionBody): KeysVersion /** * Get the key backup last version * If not supported by the server, an error is returned: {"errcode":"M_NOT_FOUND","error":"No backup found"} */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version") - fun getKeysBackupLastVersion(): Call + suspend fun getKeysBackupLastVersion(): KeysVersionResult /** * Get information about the given version. @@ -64,7 +63,7 @@ internal interface RoomKeysApi { * @param version version */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}") - fun getKeysBackupVersion(@Path("version") version: String): Call + suspend fun getKeysBackupVersion(@Path("version") version: String): KeysVersionResult /** * Update information about the given version. @@ -72,8 +71,8 @@ internal interface RoomKeysApi { * @param updateKeysBackupVersionBody the body */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}") - fun updateKeysBackupVersion(@Path("version") version: String, - @Body keysBackupVersionBody: UpdateKeysBackupVersionBody): Call + suspend fun updateKeysBackupVersion(@Path("version") version: String, + @Body keysBackupVersionBody: UpdateKeysBackupVersionBody) /* ========================================================================================== * Storing keys @@ -94,10 +93,10 @@ internal interface RoomKeysApi { * @param keyBackupData the data to send */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}") - fun storeRoomSessionData(@Path("roomId") roomId: String, - @Path("sessionId") sessionId: String, - @Query("version") version: String, - @Body keyBackupData: KeyBackupData): Call + suspend fun storeRoomSessionData(@Path("roomId") roomId: String, + @Path("sessionId") sessionId: String, + @Query("version") version: String, + @Body keyBackupData: KeyBackupData): BackupKeysResult /** * Store several keys for the given room, using the given backup version. @@ -107,9 +106,9 @@ internal interface RoomKeysApi { * @param roomKeysBackupData the data to send */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}") - fun storeRoomSessionsData(@Path("roomId") roomId: String, - @Query("version") version: String, - @Body roomKeysBackupData: RoomKeysBackupData): Call + suspend fun storeRoomSessionsData(@Path("roomId") roomId: String, + @Query("version") version: String, + @Body roomKeysBackupData: RoomKeysBackupData): BackupKeysResult /** * Store several keys, using the given backup version. @@ -118,8 +117,8 @@ internal interface RoomKeysApi { * @param keysBackupData the data to send */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys") - fun storeSessionsData(@Query("version") version: String, - @Body keysBackupData: KeysBackupData): Call + suspend fun storeSessionsData(@Query("version") version: String, + @Body keysBackupData: KeysBackupData): BackupKeysResult /* ========================================================================================== * Retrieving keys @@ -133,9 +132,9 @@ internal interface RoomKeysApi { * @param version the version of the backup, or empty String to retrieve the last version */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}") - fun getRoomSessionData(@Path("roomId") roomId: String, - @Path("sessionId") sessionId: String, - @Query("version") version: String): Call + suspend fun getRoomSessionData(@Path("roomId") roomId: String, + @Path("sessionId") sessionId: String, + @Query("version") version: String): KeyBackupData /** * Retrieve all the keys for the given room from the backup. @@ -144,8 +143,8 @@ internal interface RoomKeysApi { * @param version the version of the backup, or empty String to retrieve the last version */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}") - fun getRoomSessionsData(@Path("roomId") roomId: String, - @Query("version") version: String): Call + suspend fun getRoomSessionsData(@Path("roomId") roomId: String, + @Query("version") version: String): RoomKeysBackupData /** * Retrieve all the keys from the backup. @@ -153,7 +152,7 @@ internal interface RoomKeysApi { * @param version the version of the backup, or empty String to retrieve the last version */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys") - fun getSessionsData(@Query("version") version: String): Call + suspend fun getSessionsData(@Query("version") version: String): KeysBackupData /* ========================================================================================== * Deleting keys @@ -163,22 +162,22 @@ internal interface RoomKeysApi { * Deletes keys from the backup. */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}") - fun deleteRoomSessionData(@Path("roomId") roomId: String, - @Path("sessionId") sessionId: String, - @Query("version") version: String): Call + suspend fun deleteRoomSessionData(@Path("roomId") roomId: String, + @Path("sessionId") sessionId: String, + @Query("version") version: String) /** * Deletes keys from the backup. */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}") - fun deleteRoomSessionsData(@Path("roomId") roomId: String, - @Query("version") version: String): Call + suspend fun deleteRoomSessionsData(@Path("roomId") roomId: String, + @Query("version") version: String) /** * Deletes keys from the backup. */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys") - fun deleteSessionsData(@Query("version") version: String): Call + suspend fun deleteSessionsData(@Query("version") version: String) /* ========================================================================================== * Deleting backup @@ -188,5 +187,5 @@ internal interface RoomKeysApi { * Deletes a backup. */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}") - fun deleteBackup(@Path("version") version: String): Call + suspend fun deleteBackup(@Path("version") version: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt index 5c59cfd80e..62610a0b7b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt @@ -33,7 +33,7 @@ internal class DefaultCreateKeysBackupVersionTask @Inject constructor( override suspend fun execute(params: CreateKeysBackupVersionBody): KeysVersion { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.createKeysBackupVersion(params) + roomKeysApi.createKeysBackupVersion(params) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt index ec09da7240..7ee6f2358d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt @@ -35,7 +35,7 @@ internal class DefaultDeleteBackupTask @Inject constructor( override suspend fun execute(params: DeleteBackupTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.deleteBackup(params.version) + roomKeysApi.deleteBackup(params.version) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt index 9c477efb78..7f1b03b932 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt @@ -37,7 +37,7 @@ internal class DefaultDeleteRoomSessionDataTask @Inject constructor( override suspend fun execute(params: DeleteRoomSessionDataTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.deleteRoomSessionData( + roomKeysApi.deleteRoomSessionData( params.roomId, params.sessionId, params.version) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionsDataTask.kt index 82d022f3ab..394cc861d6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionsDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionsDataTask.kt @@ -36,7 +36,7 @@ internal class DefaultDeleteRoomSessionsDataTask @Inject constructor( override suspend fun execute(params: DeleteRoomSessionsDataTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.deleteRoomSessionsData( + roomKeysApi.deleteRoomSessionsData( params.roomId, params.version) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteSessionsDataTask.kt index e4df379963..808c6c9956 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteSessionsDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteSessionsDataTask.kt @@ -35,7 +35,7 @@ internal class DefaultDeleteSessionsDataTask @Inject constructor( override suspend fun execute(params: DeleteSessionsDataTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.deleteSessionsData(params.version) + roomKeysApi.deleteSessionsData(params.version) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupLastVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupLastVersionTask.kt index 3566ff0e68..54dbf85e30 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupLastVersionTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupLastVersionTask.kt @@ -32,7 +32,7 @@ internal class DefaultGetKeysBackupLastVersionTask @Inject constructor( override suspend fun execute(params: Unit): KeysVersionResult { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.getKeysBackupLastVersion() + roomKeysApi.getKeysBackupLastVersion() } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt index 13c99fb0f4..390873eb68 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt @@ -32,7 +32,7 @@ internal class DefaultGetKeysBackupVersionTask @Inject constructor( override suspend fun execute(params: String): KeysVersionResult { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.getKeysBackupVersion(params) + roomKeysApi.getKeysBackupVersion(params) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt index 168020d9cd..ff515ed80f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt @@ -38,7 +38,7 @@ internal class DefaultGetRoomSessionDataTask @Inject constructor( override suspend fun execute(params: GetRoomSessionDataTask.Params): KeyBackupData { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.getRoomSessionData( + roomKeysApi.getRoomSessionData( params.roomId, params.sessionId, params.version) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionsDataTask.kt index 95d5ef2e53..1b4fe2d966 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionsDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionsDataTask.kt @@ -37,7 +37,7 @@ internal class DefaultGetRoomSessionsDataTask @Inject constructor( override suspend fun execute(params: GetRoomSessionsDataTask.Params): RoomKeysBackupData { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.getRoomSessionsData( + roomKeysApi.getRoomSessionsData( params.roomId, params.version) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetSessionsDataTask.kt index e41a13e3eb..707125f4cd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetSessionsDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetSessionsDataTask.kt @@ -36,7 +36,7 @@ internal class DefaultGetSessionsDataTask @Inject constructor( override suspend fun execute(params: GetSessionsDataTask.Params): KeysBackupData { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.getSessionsData(params.version) + roomKeysApi.getSessionsData(params.version) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionDataTask.kt index 3954277e39..180aaecf82 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionDataTask.kt @@ -40,7 +40,7 @@ internal class DefaultStoreRoomSessionDataTask @Inject constructor( override suspend fun execute(params: StoreRoomSessionDataTask.Params): BackupKeysResult { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.storeRoomSessionData( + roomKeysApi.storeRoomSessionData( params.roomId, params.sessionId, params.version, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionsDataTask.kt index 4e209b4abc..d1aa9d2eb0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionsDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionsDataTask.kt @@ -39,7 +39,7 @@ internal class DefaultStoreRoomSessionsDataTask @Inject constructor( override suspend fun execute(params: StoreRoomSessionsDataTask.Params): BackupKeysResult { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.storeRoomSessionsData( + roomKeysApi.storeRoomSessionsData( params.roomId, params.version, params.roomKeysBackupData) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreSessionsDataTask.kt index a607477d21..3dbeafe9de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreSessionsDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreSessionsDataTask.kt @@ -38,7 +38,7 @@ internal class DefaultStoreSessionsDataTask @Inject constructor( override suspend fun execute(params: StoreSessionsDataTask.Params): BackupKeysResult { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.storeSessionsData( + roomKeysApi.storeSessionsData( params.version, params.keysBackupData) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/UpdateKeysBackupVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/UpdateKeysBackupVersionTask.kt index f012cd13eb..2b3d044ab7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/UpdateKeysBackupVersionTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/UpdateKeysBackupVersionTask.kt @@ -37,7 +37,7 @@ internal class DefaultUpdateKeysBackupVersionTask @Inject constructor( override suspend fun execute(params: UpdateKeysBackupVersionTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.updateKeysBackupVersion(params.version, params.keysBackupVersionBody) + roomKeysApi.updateKeysBackupVersion(params.version, params.keysBackupVersionBody) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt index 3df6312adb..d5cf749db7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt @@ -20,7 +20,6 @@ import org.matrix.android.sdk.internal.crypto.api.CryptoApi import org.matrix.android.sdk.internal.crypto.model.MXKey import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimBody -import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimResponse import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task @@ -42,8 +41,8 @@ internal class DefaultClaimOneTimeKeysForUsersDevice @Inject constructor( override suspend fun execute(params: ClaimOneTimeKeysForUsersDeviceTask.Params): MXUsersDevicesMap { val body = KeysClaimBody(oneTimeKeys = params.usersDevicesKeyTypesMap.map) - val keysClaimResponse = executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body) + val keysClaimResponse = executeRequest(globalErrorReceiver) { + cryptoApi.claimOneTimeKeysForUsersDevices(body) } val map = MXUsersDevicesMap() keysClaimResponse.oneTimeKeys?.let { oneTimeKeys -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt index 61596bb5b6..bdb8e8d137 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt @@ -42,8 +42,8 @@ internal class DefaultDeleteDeviceTask @Inject constructor( override suspend fun execute(params: DeleteDeviceTask.Params) { try { - executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams(params.userAuthParam?.asMap())) + executeRequest(globalErrorReceiver) { + cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams(params.userAuthParam?.asMap())) } } catch (throwable: Throwable) { if (params.userInteractiveAuthInterceptor == null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt index 0c17cbb43a..86f02866ae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt @@ -72,8 +72,8 @@ internal class DefaultDownloadKeysForUsers @Inject constructor( } .map { body -> async { - val result = executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.downloadKeysForUsers(body) + val result = executeRequest(globalErrorReceiver) { + cryptoApi.downloadKeysForUsers(body) } mutex.withLock { @@ -98,7 +98,7 @@ internal class DefaultDownloadKeysForUsers @Inject constructor( } else { // No need to chunk, direct request executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.downloadKeysForUsers( + cryptoApi.downloadKeysForUsers( KeysQueryBody( deviceKeys = params.userIds.associateWith { emptyList() }, token = token diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDeviceInfoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDeviceInfoTask.kt index 5f6d2e344f..9f20ea598d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDeviceInfoTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDeviceInfoTask.kt @@ -34,7 +34,7 @@ internal class DefaultGetDeviceInfoTask @Inject constructor( override suspend fun execute(params: GetDeviceInfoTask.Params): DeviceInfo { return executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.getDeviceInfo(params.deviceId) + cryptoApi.getDeviceInfo(params.deviceId) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDevicesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDevicesTask.kt index ea33a918bc..52f9f73299 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDevicesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDevicesTask.kt @@ -32,7 +32,7 @@ internal class DefaultGetDevicesTask @Inject constructor( override suspend fun execute(params: Unit): DevicesListResponse { return executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.getDevices() + cryptoApi.getDevices() } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetKeyChangesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetKeyChangesTask.kt index 4cc9ab2fcb..6e524c7fbe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetKeyChangesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetKeyChangesTask.kt @@ -39,7 +39,7 @@ internal class DefaultGetKeyChangesTask @Inject constructor( override suspend fun execute(params: GetKeyChangesTask.Params): KeyChangesResponse { return executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.getKeyChanges(params.from, params.to) + cryptoApi.getKeyChanges(params.from, params.to) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt index 5226e52b33..d6a7f3c6a0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.crypto.tasks import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI -import org.matrix.android.sdk.internal.session.room.send.SendResponse import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -36,14 +35,14 @@ internal class DefaultRedactEventTask @Inject constructor( private val globalErrorReceiver: GlobalErrorReceiver) : RedactEventTask { override suspend fun execute(params: RedactEventTask.Params): String { - val executeRequest = executeRequest(globalErrorReceiver) { - apiCall = roomAPI.redactEvent( + val response = executeRequest(globalErrorReceiver) { + roomAPI.redactEvent( txId = params.txID, roomId = params.roomId, eventId = params.eventId, reason = if (params.reason == null) emptyMap() else mapOf("reason" to params.reason) ) } - return executeRequest.eventId + return response.eventId } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt index 573f2c3a54..e1e297767b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt @@ -22,7 +22,6 @@ import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository -import org.matrix.android.sdk.internal.session.room.send.SendResponse import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -52,8 +51,8 @@ internal class DefaultSendEventTask @Inject constructor( val event = handleEncryption(params) val localId = event.eventId!! localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENDING) - val executeRequest = executeRequest(globalErrorReceiver) { - apiCall = roomAPI.send( + val response = executeRequest(globalErrorReceiver) { + roomAPI.send( localId, roomId = event.roomId ?: "", content = event.content, @@ -61,7 +60,7 @@ internal class DefaultSendEventTask @Inject constructor( ) } localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENT) - return executeRequest.eventId + return response.eventId } catch (e: Throwable) { // localEchoRepository.updateSendState(params.event.eventId!!, SendState.UNDELIVERED) throw e diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt index d2af91601b..41a5118be0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt @@ -46,14 +46,16 @@ internal class DefaultSendToDeviceTask @Inject constructor( messages = params.contentMap.map ) - return executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.sendToDevice( + return executeRequest( + globalErrorReceiver, + canRetry = true, + maxRetriesCount = 3 + ) { + cryptoApi.sendToDevice( params.eventType, params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(), sendToDeviceBody ) - isRetryable = true - maxRetryCount = 3 } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt index ab125135bb..d8b9d3cd86 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt @@ -22,7 +22,6 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository -import org.matrix.android.sdk.internal.session.room.send.SendResponse import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -45,8 +44,8 @@ internal class DefaultSendVerificationMessageTask @Inject constructor( try { localEchoRepository.updateSendState(localId, event.roomId, SendState.SENDING) - val executeRequest = executeRequest(globalErrorReceiver) { - apiCall = roomAPI.send( + val response = executeRequest(globalErrorReceiver) { + roomAPI.send( localId, roomId = event.roomId ?: "", content = event.content, @@ -54,7 +53,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor( ) } localEchoRepository.updateSendState(localId, event.roomId, SendState.SENT) - return executeRequest.eventId + return response.eventId } catch (e: Throwable) { localEchoRepository.updateSendState(localId, event.roomId, SendState.UNDELIVERED) throw e diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SetDeviceNameTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SetDeviceNameTask.kt index b835d46236..4bedb1f393 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SetDeviceNameTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SetDeviceNameTask.kt @@ -42,7 +42,7 @@ internal class DefaultSetDeviceNameTask @Inject constructor( displayName = params.deviceName ) return executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.updateDeviceInfo(params.deviceId, body) + cryptoApi.updateDeviceInfo(params.deviceId, body) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt index eb53bbbf8d..cac4dadd93 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt @@ -50,7 +50,7 @@ internal class DefaultUploadKeysTask @Inject constructor( Timber.i("## Uploading device keys -> $body") return executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.uploadKeys(body) + cryptoApi.uploadKeys(body) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt index c50faf37b1..e03e353cb1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.crypto.tasks import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.internal.crypto.api.CryptoApi -import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task @@ -36,10 +35,12 @@ internal class DefaultUploadSignaturesTask @Inject constructor( override suspend fun execute(params: UploadSignaturesTask.Params) { try { - val response = executeRequest(globalErrorReceiver) { - this.isRetryable = true - this.maxRetryCount = 10 - this.apiCall = cryptoApi.uploadSignatures(params.signatures) + val response = executeRequest( + globalErrorReceiver, + canRetry = true, + maxRetriesCount = 10 + ) { + cryptoApi.uploadSignatures(params.signatures) } if (response.failures?.isNotEmpty() == true) { throw Throwable(response.failures.toString()) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt index 14fad2ea38..08c767ba34 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto.tasks import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.internal.crypto.api.CryptoApi import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey -import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.internal.crypto.model.rest.UploadSigningKeysBody import org.matrix.android.sdk.internal.crypto.model.toRest @@ -61,8 +60,8 @@ internal class DefaultUploadSigningKeysTask @Inject constructor( } private suspend fun doRequest(uploadQuery: UploadSigningKeysBody) { - val keysQueryResponse = executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.uploadSigningKeys(uploadQuery) + val keysQueryResponse = executeRequest(globalErrorReceiver) { + cryptoApi.uploadSigningKeys(uploadQuery) } if (keysQueryResponse.failures?.isNotEmpty() == true) { throw UploadSigningKeys(keysQueryResponse.failures) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index c7fe7ab447..1daae906f2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -17,22 +17,27 @@ package org.matrix.android.sdk.internal.database import io.realm.DynamicRealm +import io.realm.FieldAttribute import io.realm.RealmMigration +import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields import org.matrix.android.sdk.internal.database.model.EditionOfEventFields +import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields import org.matrix.android.sdk.internal.database.model.RoomEntityFields import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields +import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import timber.log.Timber import javax.inject.Inject class RealmSessionStoreMigration @Inject constructor() : RealmMigration { companion object { - const val SESSION_STORE_SCHEMA_VERSION = 8L + const val SESSION_STORE_SCHEMA_VERSION = 9L } override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { @@ -46,6 +51,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { if (oldVersion <= 5) migrateTo6(realm) if (oldVersion <= 6) migrateTo7(realm) if (oldVersion <= 7) migrateTo8(realm) + if (oldVersion <= 8) migrateTo9(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -149,4 +155,43 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { ?.removeField("sourceLocalEchoEvents") ?.addRealmListField(EditAggregatedSummaryEntityFields.EDITIONS.`$`, editionOfEventSchema) } + + fun migrateTo9(realm: DynamicRealm) { + Timber.d("Step 8 -> 9") + + realm.schema.get("RoomSummaryEntity") + ?.addField(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Long::class.java, FieldAttribute.INDEXED) + ?.setNullable(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, true) + ?.addIndex(RoomSummaryEntityFields.MEMBERSHIP_STR) + ?.addIndex(RoomSummaryEntityFields.IS_DIRECT) + ?.addIndex(RoomSummaryEntityFields.VERSIONING_STATE_STR) + + ?.addField(RoomSummaryEntityFields.IS_FAVOURITE, Boolean::class.java) + ?.addIndex(RoomSummaryEntityFields.IS_FAVOURITE) + ?.addField(RoomSummaryEntityFields.IS_LOW_PRIORITY, Boolean::class.java) + ?.addIndex(RoomSummaryEntityFields.IS_LOW_PRIORITY) + ?.addField(RoomSummaryEntityFields.IS_SERVER_NOTICE, Boolean::class.java) + ?.addIndex(RoomSummaryEntityFields.IS_SERVER_NOTICE) + + ?.transform { obj -> + + val isFavorite = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any { + it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_FAVOURITE + } + obj.setBoolean(RoomSummaryEntityFields.IS_FAVOURITE, isFavorite) + + val isLowPriority = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any { + it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_LOW_PRIORITY + } + + obj.setBoolean(RoomSummaryEntityFields.IS_LOW_PRIORITY, isLowPriority) + +// XXX migrate last message origin server ts + obj.getObject(RoomSummaryEntityFields.LATEST_PREVIEWABLE_EVENT.`$`) + ?.getObject(TimelineEventEntityFields.ROOT.`$`) + ?.getLong(EventEntityFields.ORIGIN_SERVER_TS)?.let { + obj.setLong(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, it) + } + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index 2e54a4cd52..6dc70b60fc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt @@ -26,7 +26,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa private val typingUsersTracker: DefaultTypingUsersTracker) { fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary { - val tags = roomSummaryEntity.tags.map { + val tags = roomSummaryEntity.tags().map { RoomTag(it.tagName, it.tagOrder) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt index a48b081f02..e970fab397 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt @@ -39,5 +39,7 @@ internal open class RoomMemberSummaryEntity(@PrimaryKey var primaryKey: String = membershipStr = value.name } + fun getBestName() = displayName?.takeIf { it.isNotBlank() } ?: userId + companion object } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt index 37696c9082..c87ac15a78 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt @@ -16,61 +16,217 @@ package org.matrix.android.sdk.internal.database.model +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.Index +import io.realm.annotations.PrimaryKey import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.VersioningState -import io.realm.RealmList -import io.realm.RealmObject -import io.realm.annotations.PrimaryKey +import org.matrix.android.sdk.api.session.room.model.tag.RoomTag internal open class RoomSummaryEntity( - @PrimaryKey var roomId: String = "", - var displayName: String? = "", - var avatarUrl: String? = "", - var name: String? = "", - var topic: String? = "", - var latestPreviewableEvent: TimelineEventEntity? = null, - var heroes: RealmList = RealmList(), - var joinedMembersCount: Int? = 0, - var invitedMembersCount: Int? = 0, - var isDirect: Boolean = false, - var directUserId: String? = null, - var otherMemberIds: RealmList = RealmList(), - var notificationCount: Int = 0, - var highlightCount: Int = 0, - var readMarkerId: String? = null, - var hasUnreadMessages: Boolean = false, - var tags: RealmList = RealmList(), - var userDrafts: UserDraftsEntity? = null, - var breadcrumbsIndex: Int = RoomSummary.NOT_IN_BREADCRUMBS, - var canonicalAlias: String? = null, - var aliases: RealmList = RealmList(), - // this is required for querying - var flatAliases: String = "", - var isEncrypted: Boolean = false, - var encryptionEventTs: Long? = 0, - var roomEncryptionTrustLevelStr: String? = null, - var inviterId: String? = null, - var hasFailedSending: Boolean = false + @PrimaryKey var roomId: String = "" ) : RealmObject() { + var displayName: String? = "" + set(value) { + if (value != field) field = value + } + var avatarUrl: String? = "" + set(value) { + if (value != field) field = value + } + var name: String? = "" + set(value) { + if (value != field) field = value + } + var topic: String? = "" + set(value) { + if (value != field) field = value + } + + var latestPreviewableEvent: TimelineEventEntity? = null + set(value) { + if (value != field) field = value + } + + @Index + var lastActivityTime: Long? = null + set(value) { + if (value != field) field = value + } + + var heroes: RealmList = RealmList() + + var joinedMembersCount: Int? = 0 + set(value) { + if (value != field) field = value + } + + var invitedMembersCount: Int? = 0 + set(value) { + if (value != field) field = value + } + + @Index + var isDirect: Boolean = false + set(value) { + if (value != field) field = value + } + + var directUserId: String? = null + set(value) { + if (value != field) field = value + } + + var otherMemberIds: RealmList = RealmList() + + var notificationCount: Int = 0 + set(value) { + if (value != field) field = value + } + + var highlightCount: Int = 0 + set(value) { + if (value != field) field = value + } + + var readMarkerId: String? = null + set(value) { + if (value != field) field = value + } + + var hasUnreadMessages: Boolean = false + set(value) { + if (value != field) field = value + } + + private var tags: RealmList = RealmList() + + fun tags(): List = tags + + fun updateTags(newTags: List>) { + val toDelete = mutableListOf() + tags.forEach { existingTag -> + val updatedTag = newTags.firstOrNull { it.first == existingTag.tagName } + if (updatedTag == null) { + toDelete.add(existingTag) + } else { + existingTag.tagOrder = updatedTag.second + } + } + toDelete.forEach { it.deleteFromRealm() } + newTags.forEach { newTag -> + if (tags.all { it.tagName != newTag.first }) { + // we must add it + tags.add( + RoomTagEntity(newTag.first, newTag.second) + ) + } + } + + isFavourite = newTags.any { it.first == RoomTag.ROOM_TAG_FAVOURITE } + isLowPriority = newTags.any { it.first == RoomTag.ROOM_TAG_LOW_PRIORITY } + isServerNotice = newTags.any { it.first == RoomTag.ROOM_TAG_SERVER_NOTICE } + } + + @Index + var isFavourite: Boolean = false + set(value) { + if (value != field) field = value + } + + @Index + var isLowPriority: Boolean = false + set(value) { + if (value != field) field = value + } + + @Index + var isServerNotice: Boolean = false + set(value) { + if (value != field) field = value + } + + var userDrafts: UserDraftsEntity? = null + set(value) { + if (value != field) field = value + } + + var breadcrumbsIndex: Int = RoomSummary.NOT_IN_BREADCRUMBS + set(value) { + if (value != field) field = value + } + + var canonicalAlias: String? = null + set(value) { + if (value != field) field = value + } + + var aliases: RealmList = RealmList() + + fun updateAliases(newAliases: List) { + // only update underlying field if there is a diff + if (newAliases.distinct().sorted() != aliases.distinct().sorted()) { + aliases.clear() + aliases.addAll(newAliases) + flatAliases = newAliases.joinToString(separator = "|", prefix = "|") + } + } + + // this is required for querying + var flatAliases: String = "" + + var isEncrypted: Boolean = false + set(value) { + if (value != field) field = value + } + + var encryptionEventTs: Long? = 0 + set(value) { + if (value != field) field = value + } + + var roomEncryptionTrustLevelStr: String? = null + set(value) { + if (value != field) field = value + } + + var inviterId: String? = null + set(value) { + if (value != field) field = value + } + + var hasFailedSending: Boolean = false + set(value) { + if (value != field) field = value + } + + @Index private var membershipStr: String = Membership.NONE.name + var membership: Membership get() { return Membership.valueOf(membershipStr) } set(value) { - membershipStr = value.name + if (value.name != membershipStr) { + membershipStr = value.name + } } + @Index private var versioningStateStr: String = VersioningState.NONE.name var versioningState: VersioningState get() { return VersioningState.valueOf(versioningStateStr) } set(value) { - versioningStateStr = value.name + if (value.name != versioningStateStr) { + versioningStateStr = value.name + } } var roomEncryptionTrustLevel: RoomEncryptionTrustLevel? @@ -84,7 +240,9 @@ internal open class RoomSummaryEntity( } } set(value) { - roomEncryptionTrustLevelStr = value?.name + if (value?.name != roomEncryptionTrustLevelStr) { + roomEncryptionTrustLevelStr = value?.name + } } companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt index a3c741ad55..5423025823 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt @@ -38,16 +38,21 @@ internal fun isEventRead(realmConfiguration: RealmConfiguration, Realm.getInstance(realmConfiguration).use { realm -> val liveChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) ?: return@use val eventToCheck = liveChunk.timelineEvents.find(eventId) - isEventRead = if (eventToCheck == null || eventToCheck.root?.sender == userId) { - true - } else { - val readReceipt = ReadReceiptEntity.where(realm, roomId, userId).findFirst() - ?: return@use - val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.displayIndex - ?: Int.MIN_VALUE - val eventToCheckIndex = eventToCheck.displayIndex + isEventRead = when { + eventToCheck == null -> { + // This can happen in case of fast lane Event + false + } + eventToCheck.root?.sender == userId -> true + else -> { + val readReceipt = ReadReceiptEntity.where(realm, roomId, userId).findFirst() + ?: return@use + val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.displayIndex + ?: Int.MIN_VALUE + val eventToCheckIndex = eventToCheck.displayIndex - eventToCheckIndex <= readReceiptIndex + eventToCheckIndex <= readReceiptIndex + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationAPI.kt index 1816616336..c37392494f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationAPI.kt @@ -18,10 +18,9 @@ package org.matrix.android.sdk.internal.federation import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.GET internal interface FederationAPI { @GET(NetworkConstants.URI_FEDERATION_PATH + "version") - fun getVersion(): Call + suspend fun getVersion(): FederationGetVersionResult } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/GetFederationVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/GetFederationVersionTask.kt index ce35e48f6b..b7f73a606c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/GetFederationVersionTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/GetFederationVersionTask.kt @@ -28,8 +28,8 @@ internal class DefaultGetFederationVersionTask @Inject constructor( ) : GetFederationVersionTask { override suspend fun execute(params: Unit): FederationVersion { - val result = executeRequest(null) { - apiCall = federationAPI.getVersion() + val result = executeRequest(null) { + federationAPI.getVersion() } return FederationVersion( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt index 442029127d..0246bae024 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt @@ -19,38 +19,49 @@ package org.matrix.android.sdk.internal.network import kotlinx.coroutines.CancellationException import kotlinx.coroutines.delay import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.failure.MatrixError +import org.matrix.android.sdk.api.failure.getRetryDelay import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.internal.network.ssl.CertUtil -import retrofit2.Call -import retrofit2.awaitResponse +import retrofit2.HttpException import timber.log.Timber import java.io.IOException -internal suspend inline fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, - block: Request.() -> Unit) = Request(globalErrorReceiver).apply(block).execute() +/** + * Execute a request from the requestBlock and handle some of the Exception it could generate + * Ref: https://github.com/matrix-org/matrix-js-sdk/blob/develop/src/scheduler.js#L138-L175 + * + * @param globalErrorReceiver will be use to notify error such as invalid token error. See [GlobalError] + * @param canRetry if set to true, the request will be executed again in case of error, after a delay + * @param maxDelayBeforeRetry the max delay to wait before a retry + * @param maxRetriesCount the max number of retries + * @param requestBlock a suspend lambda to perform the network request + */ +internal suspend inline fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, + canRetry: Boolean = false, + maxDelayBeforeRetry: Long = 32_000L, + maxRetriesCount: Int = 4, + noinline requestBlock: suspend () -> DATA): DATA { + var currentRetryCount = 0 + var currentDelay = 1_000L -internal class Request(private val globalErrorReceiver: GlobalErrorReceiver?) { - - var isRetryable = false - var initialDelay: Long = 100L - var maxDelay: Long = 10_000L - var maxRetryCount = Int.MAX_VALUE - private var currentRetryCount = 0 - private var currentDelay = initialDelay - lateinit var apiCall: Call - - suspend fun execute(): DATA { - return try { - val response = apiCall.clone().awaitResponse() - if (response.isSuccessful) { - response.body() - ?: throw IllegalStateException("The request returned a null body") - } else { - throw response.toFailure(globalErrorReceiver) + while (true) { + try { + return requestBlock() + } catch (throwable: Throwable) { + val exception = when (throwable) { + is KotlinNullPointerException -> IllegalStateException("The request returned a null body") + is HttpException -> throwable.toFailure(globalErrorReceiver) + else -> throwable + } + + // Log some details about the request which has failed. + val request = (throwable as? HttpException)?.response()?.raw()?.request + if (request == null) { + Timber.e("Exception when executing request") + } else { + Timber.e("Exception when executing request ${request.method} ${request.url.toString().substringBefore("?")}") } - } catch (exception: Throwable) { - // Log some details about the request which has failed - Timber.e("Exception when executing request ${apiCall.request().method} ${apiCall.request().url.toString().substringBefore("?")}") // Check if this is a certificateException CertUtil.getCertificateException(exception) @@ -61,10 +72,18 @@ internal class Request(private val globalErrorReceiver: GlobalErrorR // } ?.also { unrecognizedCertificateException -> throw unrecognizedCertificateException } - if (isRetryable && currentRetryCount++ < maxRetryCount && exception.shouldBeRetried()) { + currentRetryCount++ + + if (exception is Failure.ServerError + && exception.httpCode == 429 + && exception.error.code == MatrixError.M_LIMIT_EXCEEDED + && currentRetryCount < maxRetriesCount) { + // 429, we can retry + delay(exception.getRetryDelay(1_000)) + } else if (canRetry && currentRetryCount < maxRetriesCount && exception.shouldBeRetried()) { delay(currentDelay) - currentDelay = (currentDelay * 2L).coerceAtMost(maxDelay) - return execute() + currentDelay = currentDelay.times(2L).coerceAtMost(maxDelayBeforeRetry) + // Try again (loop) } else { throw when (exception) { is IOException -> Failure.NetworkConnection(exception) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt index dd5a69dd3c..7132b4ff7a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.internal.di.MoshiProvider import kotlinx.coroutines.suspendCancellableCoroutine import okhttp3.ResponseBody +import retrofit2.HttpException import retrofit2.Response import timber.log.Timber import java.io.IOException @@ -57,6 +58,13 @@ internal fun Response.toFailure(globalErrorReceiver: GlobalErrorReceiver? return toFailure(errorBody(), code(), globalErrorReceiver) } +/** + * Convert a HttpException to a Failure, and eventually parse errorBody to convert it to a MatrixError + */ +internal fun HttpException.toFailure(globalErrorReceiver: GlobalErrorReceiver?): Failure { + return toFailure(response()?.errorBody(), code(), globalErrorReceiver) +} + /** * Convert a okhttp3 Response to a Failure, and eventually parse errorBody to convert it to a MatrixError */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GetUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GetUrlTask.kt index 16633d90ef..d0e2534e7a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GetUrlTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GetUrlTask.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.raw import com.zhuinden.monarchy.Monarchy -import okhttp3.ResponseBody import org.matrix.android.sdk.api.cache.CacheStrategy import org.matrix.android.sdk.internal.database.model.RawCacheEntity import org.matrix.android.sdk.internal.database.query.get @@ -58,8 +57,8 @@ internal class DefaultGetUrlTask @Inject constructor( } private suspend fun doRequest(url: String): String { - return executeRequest(null) { - apiCall = rawAPI.getUrl(url) + return executeRequest(null) { + rawAPI.getUrl(url) } .string() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawAPI.kt index 4b08afd711..338d94781b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawAPI.kt @@ -18,11 +18,10 @@ package org.matrix.android.sdk.internal.raw import okhttp3.ResponseBody -import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Url internal interface RawAPI { @GET - fun getUrl(@Url url: String): Call + suspend fun getUrl(@Url url: String): ResponseBody } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index 45fcc5af2d..821a9cba8c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.call.CallSignalingService import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.crypto.CryptoService +import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.group.GroupService @@ -114,6 +115,7 @@ internal class DefaultSession @Inject constructor( private val accountDataService: Lazy, private val _sharedSecretStorageService: Lazy, private val accountService: Lazy, + private val eventService: Lazy, private val defaultIdentityService: DefaultIdentityService, private val integrationManagerService: IntegrationManagerService, private val thirdPartyService: Lazy, @@ -129,6 +131,7 @@ internal class DefaultSession @Inject constructor( FilterService by filterService.get(), PushRuleService by pushRuleService.get(), PushersService by pushersService.get(), + EventService by eventService.get(), TermsService by termsService.get(), InitialSyncProgressService by initialSyncProgressService.get(), SecureStorageService by secureStorageService.get(), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index f10eb67921..e61e4ecd89 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -32,10 +32,11 @@ import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.auth.data.sessionId import org.matrix.android.sdk.api.crypto.MXCryptoConfig -import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.accountdata.AccountDataService +import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService +import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.securestorage.SecureStorageService import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService @@ -75,6 +76,7 @@ import org.matrix.android.sdk.internal.network.token.AccessTokenProvider import org.matrix.android.sdk.internal.network.token.HomeserverAccessTokenProvider import org.matrix.android.sdk.internal.session.call.CallEventProcessor import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor +import org.matrix.android.sdk.internal.session.events.DefaultEventService import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService import org.matrix.android.sdk.internal.session.initsync.DefaultInitialSyncProgressService @@ -357,6 +359,9 @@ internal abstract class SessionModule { @Binds abstract fun bindAccountDataService(service: DefaultAccountDataService): AccountDataService + @Binds + abstract fun bindEventService(service: DefaultEventService): EventService + @Binds abstract fun bindSharedSecretStorageService(service: DefaultSharedSecretStorageService): SharedSecretStorageService diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/AccountAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/AccountAPI.kt index 1db9d121a6..a04d0f2686 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/AccountAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/AccountAPI.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.account import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.POST @@ -28,7 +27,7 @@ internal interface AccountAPI { * @param params parameters to change password. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password") - fun changePassword(@Body params: ChangePasswordParams): Call + suspend fun changePassword(@Body params: ChangePasswordParams) /** * Deactivate the user account @@ -36,5 +35,5 @@ internal interface AccountAPI { * @param params the deactivate account params */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/deactivate") - fun deactivate(@Body params: DeactivateAccountParams): Call + suspend fun deactivate(@Body params: DeactivateAccountParams) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt index 1f043b0a9d..02c3735998 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt @@ -39,8 +39,8 @@ internal class DefaultChangePasswordTask @Inject constructor( override suspend fun execute(params: ChangePasswordTask.Params) { val changePasswordParams = ChangePasswordParams.create(userId, params.password, params.newPassword) try { - executeRequest(globalErrorReceiver) { - apiCall = accountAPI.changePassword(changePasswordParams) + executeRequest(globalErrorReceiver) { + accountAPI.changePassword(changePasswordParams) } } catch (throwable: Throwable) { val registrationFlowResponse = throwable.toRegistrationFlowResponse() @@ -49,8 +49,8 @@ internal class DefaultChangePasswordTask @Inject constructor( /* Avoid infinite loop */ && changePasswordParams.auth?.session == null) { // Retry with authentication - executeRequest(globalErrorReceiver) { - apiCall = accountAPI.changePassword( + executeRequest(globalErrorReceiver) { + accountAPI.changePassword( changePasswordParams.copy(auth = changePasswordParams.auth?.copy(session = registrationFlowResponse.session)) ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt index ca6b0554a9..1a8e80ab68 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt @@ -46,8 +46,8 @@ internal class DefaultDeactivateAccountTask @Inject constructor( val deactivateAccountParams = DeactivateAccountParams.create(params.userAuthParam, params.eraseAllData) val canCleanup = try { - executeRequest(globalErrorReceiver) { - apiCall = accountAPI.deactivate(deactivateAccountParams) + executeRequest(globalErrorReceiver) { + accountAPI.deactivate(deactivateAccountParams) } true } catch (throwable: Throwable) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt index 4887351709..a190ff62ac 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt @@ -21,9 +21,11 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor +import org.matrix.android.sdk.internal.session.SessionScope import timber.log.Timber import javax.inject.Inject +@SessionScope internal class CallEventProcessor @Inject constructor(private val callSignalingHandler: CallSignalingHandler) : EventInsertLiveProcessor { @@ -51,6 +53,15 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH eventsToPostProcess.add(event) } + fun shouldProcessFastLane(eventType: String): Boolean { + return eventType == EventType.CALL_INVITE + } + + suspend fun processFastLane(event: Event) { + eventsToPostProcess.add(event) + onPostProcess() + } + override suspend fun onPostProcess() { eventsToPostProcess.forEach { dispatchToCallSignalingHandlerIfNeeded(it) @@ -60,7 +71,7 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH private fun dispatchToCallSignalingHandlerIfNeeded(event: Event) { val now = System.currentTimeMillis() - // TODO might check if an invite is not closed (hangup/answsered) in the same event batch? + // TODO might check if an invite is not closed (hangup/answered) in the same event batch? event.roomId ?: return Unit.also { Timber.w("Event with no room id ${event.eventId}") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt index 7e54301f63..8d7e9e819a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt @@ -56,25 +56,25 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa fun onCallEvent(event: Event) { when (event.getClearType()) { - EventType.CALL_ANSWER -> { + EventType.CALL_ANSWER -> { handleCallAnswerEvent(event) } - EventType.CALL_INVITE -> { + EventType.CALL_INVITE -> { handleCallInviteEvent(event) } - EventType.CALL_HANGUP -> { + EventType.CALL_HANGUP -> { handleCallHangupEvent(event) } - EventType.CALL_REJECT -> { + EventType.CALL_REJECT -> { handleCallRejectEvent(event) } - EventType.CALL_CANDIDATES -> { + EventType.CALL_CANDIDATES -> { handleCallCandidatesEvent(event) } EventType.CALL_SELECT_ANSWER -> { handleCallSelectAnswerEvent(event) } - EventType.CALL_NEGOTIATE -> { + EventType.CALL_NEGOTIATE -> { handleCallNegotiateEvent(event) } } @@ -168,6 +168,14 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa return } val content = event.getClearContent().toModel() ?: return + + content.callId ?: return + if (activeCallHandler.getCallWithId(content.callId) != null) { + // Call is already known, maybe due to fast lane. Ignore + Timber.d("Ignoring already known call invite") + return + } + val incomingCall = mxCallFactory.createIncomingCall( roomId = event.roomId, opponentUserId = event.senderId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt index b21ec1113a..d53ddb7371 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt @@ -31,7 +31,7 @@ internal class DefaultGetTurnServerTask @Inject constructor(private val voipAPI: override suspend fun execute(params: Params): TurnServerResponse { return executeRequest(globalErrorReceiver) { - apiCall = voipAPI.getTurnServer() + voipAPI.getTurnServer() } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/VoipApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/VoipApi.kt index 72c6c58f27..469faaae74 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/VoipApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/VoipApi.kt @@ -18,11 +18,10 @@ package org.matrix.android.sdk.internal.session.call import org.matrix.android.sdk.api.session.call.TurnServerResponse import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.GET internal interface VoipApi { @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "voip/turnServer") - fun getTurnServer(): Call + suspend fun getTurnServer(): TurnServerResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt index 6a50f3ee37..19bc7e1908 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.directory import org.matrix.android.sdk.internal.network.NetworkConstants import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasBody import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET @@ -33,7 +32,7 @@ internal interface DirectoryAPI { * @param roomAlias the room alias. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") - fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): Call + suspend fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): RoomAliasDescription /** * Get the room directory visibility. @@ -41,7 +40,7 @@ internal interface DirectoryAPI { * @param roomId the room id. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/list/room/{roomId}") - fun getRoomDirectoryVisibility(@Path("roomId") roomId: String): Call + suspend fun getRoomDirectoryVisibility(@Path("roomId") roomId: String): RoomDirectoryVisibilityJson /** * Set the room directory visibility. @@ -50,21 +49,21 @@ internal interface DirectoryAPI { * @param body the body containing the new directory visibility */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/list/room/{roomId}") - fun setRoomDirectoryVisibility(@Path("roomId") roomId: String, - @Body body: RoomDirectoryVisibilityJson): Call + suspend fun setRoomDirectoryVisibility(@Path("roomId") roomId: String, + @Body body: RoomDirectoryVisibilityJson) /** * Add alias to the room. * @param roomAlias the room alias. */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") - fun addRoomAlias(@Path("roomAlias") roomAlias: String, - @Body body: AddRoomAliasBody): Call + suspend fun addRoomAlias(@Path("roomAlias") roomAlias: String, + @Body body: AddRoomAliasBody) /** * Delete a room alias * @param roomAlias the room alias. */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") - fun deleteRoomAlias(@Path("roomAlias") roomAlias: String): Call + suspend fun deleteRoomAlias(@Path("roomAlias") roomAlias: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt new file mode 100644 index 0000000000..d7e9ef2ee0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.events + +import org.matrix.android.sdk.api.session.events.EventService +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.session.call.CallEventProcessor +import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask +import javax.inject.Inject + +internal class DefaultEventService @Inject constructor( + private val getEventTask: GetEventTask, + private val callEventProcessor: CallEventProcessor +) : EventService { + + override suspend fun getEvent(roomId: String, eventId: String): Event { + val event = getEventTask.execute(GetEventTask.Params(roomId, eventId)) + + // Fast lane to the call event processors: try to make the incoming call ring faster + if (callEventProcessor.shouldProcessFastLane(event.getClearType())) { + callEventProcessor.processFastLane(event) + } + + return event + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/EventExt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/EventExt.kt new file mode 100644 index 0000000000..91e709e464 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/EventExt.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.events + +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.RoomMemberContent + +internal fun Event.getFixedRoomMemberContent(): RoomMemberContent? { + val content = content.toModel() + // if user is leaving, we should grab his last name and avatar from prevContent + return if (content?.membership?.isLeft() == true) { + val prevContent = resolvedPrevContent().toModel() + content.copy( + displayName = prevContent?.displayName, + avatarUrl = prevContent?.avatarUrl + ) + } else { + content + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt index 285bd51d38..2809dea23b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.filter import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST @@ -32,8 +31,8 @@ internal interface FilterApi { * @param body the Json representation of a FilterBody object */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter") - fun uploadFilter(@Path("userId") userId: String, - @Body body: Filter): Call + suspend fun uploadFilter(@Path("userId") userId: String, + @Body body: Filter): FilterResponse /** * Gets a filter with a given filterId from the homeserver @@ -43,6 +42,6 @@ internal interface FilterApi { * @return Filter */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter/{filterId}") - fun getFilterById(@Path("userId") userId: String, - @Path("filterId") filterId: String): Call + suspend fun getFilterById(@Path("userId") userId: String, + @Path("filterId") filterId: String): Filter } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt index d42962d54a..3cac89ce28 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt @@ -59,9 +59,9 @@ internal class DefaultSaveFilterTask @Inject constructor( } val updated = filterRepository.storeFilter(filterBody, roomFilter) if (updated) { - val filterResponse = executeRequest(globalErrorReceiver) { + val filterResponse = executeRequest(globalErrorReceiver) { // TODO auto retry - apiCall = filterAPI.uploadFilter(userId, filterBody) + filterAPI.uploadFilter(userId, filterBody) } filterRepository.storeFilterId(filterBody, filterResponse.filterId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt index 9836164aec..4e0ee3422b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt @@ -64,14 +64,14 @@ internal class DefaultGetGroupDataTask @Inject constructor( } Timber.v("Fetch data for group with ids: ${groupIds.joinToString(";")}") val data = groupIds.map { groupId -> - val groupSummary = executeRequest(globalErrorReceiver) { - apiCall = groupAPI.getSummary(groupId) + val groupSummary = executeRequest(globalErrorReceiver) { + groupAPI.getSummary(groupId) } - val groupRooms = executeRequest(globalErrorReceiver) { - apiCall = groupAPI.getRooms(groupId) + val groupRooms = executeRequest(globalErrorReceiver) { + groupAPI.getRooms(groupId) } - val groupUsers = executeRequest(globalErrorReceiver) { - apiCall = groupAPI.getUsers(groupId) + val groupUsers = executeRequest(globalErrorReceiver) { + groupAPI.getUsers(groupId) } GroupData(groupId, groupSummary, groupRooms, groupUsers) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupAPI.kt index 004112578c..58dcc57dd6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupAPI.kt @@ -20,7 +20,6 @@ import org.matrix.android.sdk.internal.network.NetworkConstants import org.matrix.android.sdk.internal.session.group.model.GroupRooms import org.matrix.android.sdk.internal.session.group.model.GroupSummaryResponse import org.matrix.android.sdk.internal.session.group.model.GroupUsers -import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Path @@ -32,7 +31,7 @@ internal interface GroupAPI { * @param groupId the group id */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/summary") - fun getSummary(@Path("groupId") groupId: String): Call + suspend fun getSummary(@Path("groupId") groupId: String): GroupSummaryResponse /** * Request the rooms list. @@ -40,7 +39,7 @@ internal interface GroupAPI { * @param groupId the group id */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/rooms") - fun getRooms(@Path("groupId") groupId: String): Call + suspend fun getRooms(@Path("groupId") groupId: String): GroupRooms /** * Request the users list. @@ -48,5 +47,5 @@ internal interface GroupAPI { * @param groupId the group id */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/users") - fun getUsers(@Path("groupId") groupId: String): Call + suspend fun getUsers(@Path("groupId") groupId: String): GroupUsers } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt index 8242edac84..7de0cc9592 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.homeserver import org.matrix.android.sdk.internal.auth.version.Versions import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.GET internal interface CapabilitiesAPI { @@ -26,17 +25,17 @@ internal interface CapabilitiesAPI { * Request the homeserver capabilities */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "capabilities") - fun getCapabilities(): Call + suspend fun getCapabilities(): GetCapabilitiesResult /** * Request the versions */ @GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions") - fun getVersions(): Call + suspend fun getVersions(): Versions /** * Ping the homeserver. We do not care about the returned data, so there is no use to parse them */ @GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions") - fun ping(): Call + suspend fun ping() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt index 84c9132d61..740370123f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt @@ -71,20 +71,20 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( } val capabilities = runCatching { - executeRequest(globalErrorReceiver) { - apiCall = capabilitiesAPI.getCapabilities() + executeRequest(globalErrorReceiver) { + capabilitiesAPI.getCapabilities() } }.getOrNull() val mediaConfig = runCatching { - executeRequest(globalErrorReceiver) { - apiCall = mediaAPI.getMediaConfig() + executeRequest(globalErrorReceiver) { + mediaAPI.getMediaConfig() } }.getOrNull() val versions = runCatching { - executeRequest(null) { - apiCall = capabilitiesAPI.getVersions() + executeRequest(null) { + capabilitiesAPI.getVersions() } }.getOrNull() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerPinger.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerPinger.kt index 522097acbf..bb526adf4a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerPinger.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerPinger.kt @@ -34,8 +34,8 @@ internal class HomeServerPinger @Inject constructor(private val taskExecutor: Ta suspend fun canReachHomeServer(): Boolean { return try { - executeRequest(null) { - apiCall = capabilitiesAPI.ping() + executeRequest(null) { + capabilitiesAPI.ping() } true } catch (throwable: Throwable) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt index 7e2702e70d..e9e4d17e3a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt @@ -26,7 +26,6 @@ import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestOwn import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenForEmailBody import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenForMsisdnBody import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenResponse -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST @@ -43,20 +42,20 @@ internal interface IdentityAPI { * Ref: https://matrix.org/docs/spec/identity_service/latest#get-matrix-identity-v2-account */ @GET(NetworkConstants.URI_IDENTITY_PATH_V2 + "account") - fun getAccount(): Call + suspend fun getAccount(): IdentityAccountResponse /** * Logs out the access token, preventing it from being used to authenticate future requests to the server. */ @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "account/logout") - fun logout(): Call + suspend fun logout() /** * Request the hash detail to request a bunch of 3PIDs * Ref: https://matrix.org/docs/spec/identity_service/latest#get-matrix-identity-v2-hash-details */ @GET(NetworkConstants.URI_IDENTITY_PATH_V2 + "hash_details") - fun hashDetails(): Call + suspend fun hashDetails(): IdentityHashDetailResponse /** * Request a bunch of 3PIDs @@ -65,7 +64,7 @@ internal interface IdentityAPI { * @param body the body request */ @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "lookup") - fun lookup(@Body body: IdentityLookUpParams): Call + suspend fun lookup(@Body body: IdentityLookUpParams): IdentityLookUpResponse /** * Create a session to change the bind status of an email to an identity server @@ -75,7 +74,7 @@ internal interface IdentityAPI { * @return the sid */ @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/email/requestToken") - fun requestTokenToBindEmail(@Body body: IdentityRequestTokenForEmailBody): Call + suspend fun requestTokenToBindEmail(@Body body: IdentityRequestTokenForEmailBody): IdentityRequestTokenResponse /** * Create a session to change the bind status of an phone number to an identity server @@ -85,7 +84,7 @@ internal interface IdentityAPI { * @return the sid */ @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/msisdn/requestToken") - fun requestTokenToBindMsisdn(@Body body: IdentityRequestTokenForMsisdnBody): Call + suspend fun requestTokenToBindMsisdn(@Body body: IdentityRequestTokenForMsisdnBody): IdentityRequestTokenResponse /** * Validate ownership of an email address, or a phone number. @@ -94,6 +93,6 @@ internal interface IdentityAPI { * - https://matrix.org/docs/spec/identity_service/latest#post-matrix-identity-v2-validate-email-submittoken */ @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/{medium}/submitToken") - fun submitToken(@Path("medium") medium: String, - @Body body: IdentityRequestOwnershipParams): Call + suspend fun submitToken(@Path("medium") medium: String, + @Body body: IdentityRequestOwnershipParams): SuccessResult } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt index fd6e1163ef..1671859585 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.identity import org.matrix.android.sdk.internal.network.NetworkConstants import org.matrix.android.sdk.internal.session.identity.model.IdentityRegisterResponse import org.matrix.android.sdk.internal.session.openid.RequestOpenIdTokenResponse -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST @@ -40,18 +39,18 @@ internal interface IdentityAuthAPI { * @return 200 in case of success */ @GET(NetworkConstants.URI_IDENTITY_PREFIX_PATH) - fun ping(): Call + suspend fun ping() /** * Ping v1 will be used to check outdated Identity server */ @GET("_matrix/identity/api/v1") - fun pingV1(): Call + suspend fun pingV1() /** * Exchanges an OpenID token from the homeserver for an access token to access the identity server. * The request body is the same as the values returned by /openid/request_token in the Client-Server API. */ @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "account/register") - fun register(@Body openIdToken: RequestOpenIdTokenResponse): Call + suspend fun register(@Body openIdToken: RequestOpenIdTokenResponse): IdentityRegisterResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt index 67f3b2aa56..4f6e906766 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt @@ -83,7 +83,7 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor( return try { LookUpData(hashedAddresses, executeRequest(null) { - apiCall = identityAPI.lookup(IdentityLookUpParams( + identityAPI.lookup(IdentityLookUpParams( hashedAddresses, IdentityHashDetailResponse.ALGORITHM_SHA256, hashDetailResponse.pepper @@ -126,7 +126,7 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor( private suspend fun fetchHashDetails(identityAPI: IdentityAPI): IdentityHashDetailResponse { return executeRequest(null) { - apiCall = identityAPI.hashDetails() + identityAPI.hashDetails() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityDisconnectTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityDisconnectTask.kt index 50e24f1245..fc84a144fe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityDisconnectTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityDisconnectTask.kt @@ -42,8 +42,8 @@ internal class DefaultIdentityDisconnectTask @Inject constructor( return } - executeRequest(null) { - apiCall = identityAPI.logout() + executeRequest(null) { + identityAPI.logout() } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityPingTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityPingTask.kt index b0d33811bd..fca9408d9c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityPingTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityPingTask.kt @@ -33,14 +33,14 @@ internal class DefaultIdentityPingTask @Inject constructor() : IdentityPingTask override suspend fun execute(params: IdentityPingTask.Params) { try { - executeRequest(null) { - apiCall = params.identityAuthAPI.ping() + executeRequest(null) { + params.identityAuthAPI.ping() } } catch (throwable: Throwable) { if (throwable is Failure.ServerError && throwable.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { // Check if API v1 is available - executeRequest(null) { - apiCall = params.identityAuthAPI.pingV1() + executeRequest(null) { + params.identityAuthAPI.pingV1() } // API V1 is responding, but not V2 -> Outdated throw IdentityServiceError.OutdatedIdentityServer diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRegisterTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRegisterTask.kt index 19215f353a..8cc854bd94 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRegisterTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRegisterTask.kt @@ -33,7 +33,7 @@ internal class DefaultIdentityRegisterTask @Inject constructor() : IdentityRegis override suspend fun execute(params: IdentityRegisterTask.Params): IdentityRegisterResponse { return executeRequest(null) { - apiCall = params.identityAuthAPI.register(params.openIdTokenResponse) + params.identityAuthAPI.register(params.openIdTokenResponse) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt index bd4cd763f0..9c89048176 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt @@ -25,7 +25,6 @@ import org.matrix.android.sdk.internal.session.identity.data.IdentityPendingBind import org.matrix.android.sdk.internal.session.identity.data.IdentityStore import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenForEmailBody import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenForMsisdnBody -import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenResponse import org.matrix.android.sdk.internal.task.Task import java.util.UUID import javax.inject.Inject @@ -56,8 +55,8 @@ internal class DefaultIdentityRequestTokenForBindingTask @Inject constructor( val clientSecret = identityPendingBinding?.clientSecret ?: UUID.randomUUID().toString() val sendAttempt = identityPendingBinding?.sendAttempt?.inc() ?: 1 - val tokenResponse = executeRequest(null) { - apiCall = when (params.threePid) { + val tokenResponse = executeRequest(null) { + when (params.threePid) { is ThreePid.Email -> identityAPI.requestTokenToBindEmail(IdentityRequestTokenForEmailBody( clientSecret = clientSecret, sendAttempt = sendAttempt, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentitySubmitTokenForBindingTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentitySubmitTokenForBindingTask.kt index ebc71c715d..f884e2816d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentitySubmitTokenForBindingTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentitySubmitTokenForBindingTask.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.identity import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.identity.toMedium -import org.matrix.android.sdk.internal.auth.registration.SuccessResult import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.identity.data.IdentityStore @@ -44,8 +43,8 @@ internal class DefaultIdentitySubmitTokenForBindingTask @Inject constructor( val identityAPI = getIdentityApiAndEnsureTerms(identityApiProvider, userId) val identityPendingBinding = identityStore.getPendingBinding(params.threePid) ?: throw IdentityServiceError.NoCurrentBindingError - val tokenResponse = executeRequest(null) { - apiCall = identityAPI.submitToken( + val tokenResponse = executeRequest(null) { + identityAPI.submitToken( params.threePid.toMedium(), IdentityRequestOwnershipParams( clientSecret = identityPendingBinding.clientSecret, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityTaskHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityTaskHelper.kt index d3aecce381..d06b157f22 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityTaskHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityTaskHelper.kt @@ -18,14 +18,13 @@ package org.matrix.android.sdk.internal.session.identity import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.internal.network.executeRequest -import org.matrix.android.sdk.internal.session.identity.model.IdentityAccountResponse internal suspend fun getIdentityApiAndEnsureTerms(identityApiProvider: IdentityApiProvider, userId: String): IdentityAPI { val identityAPI = identityApiProvider.identityApi ?: throw IdentityServiceError.NoIdentityServerConfigured // Always check that we have access to the service (regarding terms) - val identityAccountResponse = executeRequest(null) { - apiCall = identityAPI.getAccount() + val identityAccountResponse = executeRequest(null) { + identityAPI.getAccount() } assert(userId == identityAccountResponse.userId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt index d85e471f1d..e707c2351c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt @@ -65,8 +65,8 @@ internal class DefaultGetPreviewUrlTask @Inject constructor( } private suspend fun doRequest(url: String, timestamp: Long?): PreviewUrlData { - return executeRequest(globalErrorReceiver) { - apiCall = mediaAPI.getPreviewUrlData(url, timestamp) + return executeRequest(globalErrorReceiver) { + mediaAPI.getPreviewUrlData(url, timestamp) } .toPreviewUrlData(url) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt index 32305cd4e4..fd906f0dc8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt @@ -36,7 +36,7 @@ internal class DefaultGetRawPreviewUrlTask @Inject constructor( override suspend fun execute(params: GetRawPreviewUrlTask.Params): JsonDict { return executeRequest(globalErrorReceiver) { - apiCall = mediaAPI.getPreviewUrlData(params.url, params.timestamp) + mediaAPI.getPreviewUrlData(params.url, params.timestamp) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt index bbb4f1e06a..9ee1d26cdc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.media import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Query @@ -28,7 +27,7 @@ internal interface MediaAPI { * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-media-r0-config */ @GET(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "config") - fun getMediaConfig(): Call + suspend fun getMediaConfig(): GetMediaConfigResult /** * Get information about a URL for the client. Typically this is called when a client @@ -39,5 +38,5 @@ internal interface MediaAPI { * if it does not have the requested version available. */ @GET(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "preview_url") - fun getPreviewUrlData(@Query("url") url: String, @Query("ts") ts: Long?): Call + suspend fun getPreviewUrlData(@Query("url") url: String, @Query("ts") ts: Long?): JsonDict } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt index e00d2ff26c..38f6b08b43 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt @@ -16,8 +16,10 @@ package org.matrix.android.sdk.internal.session.notification import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.pushrules.Action import org.matrix.android.sdk.api.pushrules.PushRuleService import org.matrix.android.sdk.api.pushrules.RuleKind +import org.matrix.android.sdk.api.pushrules.RuleScope import org.matrix.android.sdk.api.pushrules.RuleSetKey import org.matrix.android.sdk.api.pushrules.getActions import org.matrix.android.sdk.api.pushrules.rest.PushRule @@ -45,6 +47,7 @@ internal class DefaultPushRuleService @Inject constructor( private val addPushRuleTask: AddPushRuleTask, private val updatePushRuleActionsTask: UpdatePushRuleActionsTask, private val removePushRuleTask: RemovePushRuleTask, + private val pushRuleFinder: PushRuleFinder, private val taskExecutor: TaskExecutor, @SessionDatabase private val monarchy: Monarchy ) : PushRuleService { @@ -130,6 +133,12 @@ internal class DefaultPushRuleService @Inject constructor( } } + override fun getActions(event: Event): List { + val rules = getPushRules(RuleScope.GLOBAL).getAllRules() + + return pushRuleFinder.fulfilledBingRule(event, rules)?.getActions().orEmpty() + } + // fun processEvents(events: List) { // var hasDoneSomething = false // events.forEach { event -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt index 54883b51e6..0ece07fc15 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt @@ -16,9 +16,7 @@ package org.matrix.android.sdk.internal.session.notification -import org.matrix.android.sdk.api.pushrules.ConditionResolver import org.matrix.android.sdk.api.pushrules.rest.PushRule -import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.sync.model.RoomsSyncResponse @@ -35,7 +33,7 @@ internal interface ProcessEventForPushTask : Task - fulfilledBingRule(event, params.rules)?.let { + pushRuleFinder.fulfilledBingRule(event, params.rules)?.let { Timber.v("[PushRules] Rule $it match for event ${event.eventId}") defaultPushRuleService.dispatchBing(event, it) } @@ -94,13 +92,4 @@ internal class DefaultProcessEventForPushTask @Inject constructor( defaultPushRuleService.dispatchFinish() } - - private fun fulfilledBingRule(event: Event, rules: List): PushRule? { - return rules.firstOrNull { rule -> - // All conditions must hold true for an event in order to apply the action for the event. - rule.enabled && rule.conditions?.all { - it.asExecutableCondition(rule)?.isSatisfied(event, conditionResolver) ?: false - } ?: false - } - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt new file mode 100644 index 0000000000..6e302d373d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.notification + +import org.matrix.android.sdk.api.pushrules.ConditionResolver +import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.events.model.Event +import javax.inject.Inject + +internal class PushRuleFinder @Inject constructor( + private val conditionResolver: ConditionResolver +) { + fun fulfilledBingRule(event: Event, rules: List): PushRule? { + return rules.firstOrNull { rule -> + // All conditions must hold true for an event in order to apply the action for the event. + rule.enabled && rule.conditions?.all { + it.asExecutableCondition(rule)?.isSatisfied(event, conditionResolver) ?: false + } ?: false + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/GetOpenIdTokenTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/GetOpenIdTokenTask.kt index f83c6b770a..8481a6ab93 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/GetOpenIdTokenTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/GetOpenIdTokenTask.kt @@ -31,7 +31,7 @@ internal class DefaultGetOpenIdTokenTask @Inject constructor( override suspend fun execute(params: Unit): RequestOpenIdTokenResponse { return executeRequest(globalErrorReceiver) { - apiCall = openIdAPI.openIdToken(userId) + openIdAPI.openIdToken(userId) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt index 4614d82453..ed090b845d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.openid import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.POST import retrofit2.http.Path @@ -34,6 +33,6 @@ internal interface OpenIdAPI { * @param userId the user id */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/openid/request_token") - fun openIdToken(@Path("userId") userId: String, - @Body body: JsonDict = emptyMap()): Call + suspend fun openIdToken(@Path("userId") userId: String, + @Body body: JsonDict = emptyMap()): RequestOpenIdTokenResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt index 6d6d70bb0d..678d399428 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt @@ -50,13 +50,14 @@ internal class DefaultAddThreePidTask @Inject constructor( val clientSecret = UUID.randomUUID().toString() val sendAttempt = 1 - val result = executeRequest(globalErrorReceiver) { - val body = AddEmailBody( - clientSecret = clientSecret, - email = threePid.email, - sendAttempt = sendAttempt - ) - apiCall = profileAPI.addEmail(body) + val body = AddEmailBody( + clientSecret = clientSecret, + email = threePid.email, + sendAttempt = sendAttempt + ) + + val result = executeRequest(globalErrorReceiver) { + profileAPI.addEmail(body) } // Store as a pending three pid @@ -84,14 +85,15 @@ internal class DefaultAddThreePidTask @Inject constructor( val countryCode = parsedNumber.countryCode val country = phoneNumberUtil.getRegionCodeForCountryCode(countryCode) - val result = executeRequest(globalErrorReceiver) { - val body = AddMsisdnBody( - clientSecret = clientSecret, - country = country, - phoneNumber = parsedNumber.nationalNumber.toString(), - sendAttempt = sendAttempt - ) - apiCall = profileAPI.addMsisdn(body) + val body = AddMsisdnBody( + clientSecret = clientSecret, + country = country, + phoneNumber = parsedNumber.nationalNumber.toString(), + sendAttempt = sendAttempt + ) + + val result = executeRequest(globalErrorReceiver) { + profileAPI.addMsisdn(body) } // Store as a pending three pid diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt index a37e5380bc..87e51181e6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt @@ -43,8 +43,8 @@ internal class DefaultBindThreePidsTask @Inject constructor(private val profileA val identityServerAccessToken = accessTokenProvider.getToken() ?: throw IdentityServiceError.NoIdentityServerConfigured val identityPendingBinding = identityStore.getPendingBinding(params.threePid) ?: throw IdentityServiceError.NoCurrentBindingError - executeRequest(globalErrorReceiver) { - apiCall = profileAPI.bindThreePid( + executeRequest(globalErrorReceiver) { + profileAPI.bindThreePid( BindThreePidBody( clientSecret = identityPendingBinding.clientSecret, identityServerUrlWithoutProtocol = identityServerUrlWithoutProtocol, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt index 3549f3613f..7b7617aa80 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt @@ -34,12 +34,12 @@ internal class DefaultDeleteThreePidTask @Inject constructor( private val globalErrorReceiver: GlobalErrorReceiver) : DeleteThreePidTask() { override suspend fun execute(params: Params) { - executeRequest(globalErrorReceiver) { - val body = DeleteThreePidBody( - medium = params.threePid.toMedium(), - address = params.threePid.value - ) - apiCall = profileAPI.deleteThreePid(body) + val body = DeleteThreePidBody( + medium = params.threePid.toMedium(), + address = params.threePid.value + ) + executeRequest(globalErrorReceiver) { + profileAPI.deleteThreePid(body) } // We do not really care about the result for the moment diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt index c2a38af093..5f063365e0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt @@ -61,13 +61,13 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor( ?: throw IllegalArgumentException("unknown threepid") try { - executeRequest(globalErrorReceiver) { + executeRequest(globalErrorReceiver) { val body = FinalizeAddThreePidBody( clientSecret = pendingThreePids.clientSecret, sid = pendingThreePids.sid, auth = params.userAuthParam?.asMap() ) - apiCall = profileAPI.finalizeAddThreePid(body) + profileAPI.finalizeAddThreePid(body) } true } catch (throwable: Throwable) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt index ed60c4a368..fed4288f84 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt @@ -34,7 +34,7 @@ internal class DefaultGetProfileInfoTask @Inject constructor(private val profile override suspend fun execute(params: Params): JsonDict { return executeRequest(globalErrorReceiver) { - apiCall = profileAPI.getProfile(params.userId) + profileAPI.getProfile(params.userId) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt index 7794f578b0..5113b821e8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt @@ -21,7 +21,6 @@ import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.auth.registration.SuccessResult import org.matrix.android.sdk.internal.auth.registration.ValidationCodeBody import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST @@ -37,70 +36,70 @@ internal interface ProfileAPI { * @param userId the user id to fetch profile info */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}") - fun getProfile(@Path("userId") userId: String): Call + suspend fun getProfile(@Path("userId") userId: String): JsonDict /** * List all 3PIDs linked to the Matrix user account. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid") - fun getThreePIDs(): Call + suspend fun getThreePIDs(): AccountThreePidsResponse /** * Change user display name */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}/displayname") - fun setDisplayName(@Path("userId") userId: String, - @Body body: SetDisplayNameBody): Call + suspend fun setDisplayName(@Path("userId") userId: String, + @Body body: SetDisplayNameBody) /** * Change user avatar url. */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}/avatar_url") - fun setAvatarUrl(@Path("userId") userId: String, - @Body body: SetAvatarUrlBody): Call + suspend fun setAvatarUrl(@Path("userId") userId: String, + @Body body: SetAvatarUrlBody) /** * Bind a threePid * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-account-3pid-bind */ @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/bind") - fun bindThreePid(@Body body: BindThreePidBody): Call + suspend fun bindThreePid(@Body body: BindThreePidBody) /** * Unbind a threePid * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-account-3pid-unbind */ @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/unbind") - fun unbindThreePid(@Body body: UnbindThreePidBody): Call + suspend fun unbindThreePid(@Body body: UnbindThreePidBody): UnbindThreePidResponse /** * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-email-requesttoken */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/email/requestToken") - fun addEmail(@Body body: AddEmailBody): Call + suspend fun addEmail(@Body body: AddEmailBody): AddEmailResponse /** * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-msisdn-requesttoken */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/msisdn/requestToken") - fun addMsisdn(@Body body: AddMsisdnBody): Call + suspend fun addMsisdn(@Body body: AddMsisdnBody): AddMsisdnResponse /** * Validate Msisdn code (same model than for Identity server API) */ @POST - fun validateMsisdn(@Url url: String, - @Body params: ValidationCodeBody): Call + suspend fun validateMsisdn(@Url url: String, + @Body params: ValidationCodeBody): SuccessResult /** * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-add */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/add") - fun finalizeAddThreePid(@Body body: FinalizeAddThreePidBody): Call + suspend fun finalizeAddThreePid(@Body body: FinalizeAddThreePidBody) /** * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-delete */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/delete") - fun deleteThreePid(@Body body: DeleteThreePidBody): Call + suspend fun deleteThreePid(@Body body: DeleteThreePidBody): DeleteThreePidResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/RefreshUserThreePidsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/RefreshUserThreePidsTask.kt index 552ad874ee..8a064b4fd1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/RefreshUserThreePidsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/RefreshUserThreePidsTask.kt @@ -33,8 +33,8 @@ internal class DefaultRefreshUserThreePidsTask @Inject constructor(private val p private val globalErrorReceiver: GlobalErrorReceiver) : RefreshUserThreePidsTask() { override suspend fun execute(params: Unit) { - val accountThreePidsResponse = executeRequest(globalErrorReceiver) { - apiCall = profileAPI.getThreePIDs() + val accountThreePidsResponse = executeRequest(globalErrorReceiver) { + profileAPI.getThreePIDs() } Timber.d("Get ${accountThreePidsResponse.threePids?.size} threePids") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt index b29153d665..a7d116d919 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt @@ -33,11 +33,11 @@ internal class DefaultSetAvatarUrlTask @Inject constructor( private val globalErrorReceiver: GlobalErrorReceiver) : SetAvatarUrlTask() { override suspend fun execute(params: Params) { + val body = SetAvatarUrlBody( + avatarUrl = params.newAvatarUrl + ) return executeRequest(globalErrorReceiver) { - val body = SetAvatarUrlBody( - avatarUrl = params.newAvatarUrl - ) - apiCall = profileAPI.setAvatarUrl(params.userId, body) + profileAPI.setAvatarUrl(params.userId, body) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt index 3f236bc589..61d3042310 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt @@ -33,11 +33,11 @@ internal class DefaultSetDisplayNameTask @Inject constructor( private val globalErrorReceiver: GlobalErrorReceiver) : SetDisplayNameTask() { override suspend fun execute(params: Params) { + val body = SetDisplayNameBody( + displayName = params.newDisplayName + ) return executeRequest(globalErrorReceiver) { - val body = SetDisplayNameBody( - displayName = params.newDisplayName - ) - apiCall = profileAPI.setDisplayName(params.userId, body) + profileAPI.setDisplayName(params.userId, body) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/UnbindThreePidsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/UnbindThreePidsTask.kt index 3439f6f840..df8a1c97ff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/UnbindThreePidsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/UnbindThreePidsTask.kt @@ -39,8 +39,8 @@ internal class DefaultUnbindThreePidsTask @Inject constructor(private val profil val identityServerUrlWithoutProtocol = identityStore.getIdentityServerUrlWithoutProtocol() ?: throw IdentityServiceError.NoIdentityServerConfigured - return executeRequest(globalErrorReceiver) { - apiCall = profileAPI.unbindThreePid( + return executeRequest(globalErrorReceiver) { + profileAPI.unbindThreePid( UnbindThreePidBody( identityServerUrlWithoutProtocol, params.threePid.toMedium(), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt index efb6c6e836..c898fc6c5c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.profile import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.identity.ThreePid -import org.matrix.android.sdk.internal.auth.registration.SuccessResult import org.matrix.android.sdk.internal.auth.registration.ValidationCodeBody import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity import org.matrix.android.sdk.internal.di.SessionDatabase @@ -58,8 +57,8 @@ internal class DefaultValidateSmsCodeTask @Inject constructor( sid = pendingThreePids.sid, code = params.code ) - val result = executeRequest(globalErrorReceiver) { - apiCall = profileAPI.validateMsisdn(url, body) + val result = executeRequest(globalErrorReceiver) { + profileAPI.validateMsisdn(url, body) } if (!result.isSuccess()) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddHttpPusherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddHttpPusherWorker.kt index d0f7cbfca3..c9d7ad2193 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddHttpPusherWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddHttpPusherWorker.kt @@ -81,8 +81,8 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters) } private suspend fun setPusher(pusher: JsonPusher) { - executeRequest(globalErrorReceiver) { - apiCall = pushersAPI.setPusher(pusher) + executeRequest(globalErrorReceiver) { + pushersAPI.setPusher(pusher) } monarchy.awaitTransaction { realm -> val echo = PusherEntity.where(realm, pusher.pushKey).findFirst() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt index 03748b1528..b217687168 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt @@ -36,7 +36,7 @@ internal class DefaultAddPushRuleTask @Inject constructor( override suspend fun execute(params: AddPushRuleTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = pushRulesApi.addRule(params.kind.value, params.pushRule.ruleId, params.pushRule) + pushRulesApi.addRule(params.kind.value, params.pushRule.ruleId, params.pushRule) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt index 9fb2d51664..8cf861d285 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt @@ -15,7 +15,6 @@ */ package org.matrix.android.sdk.internal.session.pushers -import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task @@ -35,8 +34,8 @@ internal class DefaultGetPushRulesTask @Inject constructor( ) : GetPushRulesTask { override suspend fun execute(params: GetPushRulesTask.Params) { - val response = executeRequest(globalErrorReceiver) { - apiCall = pushRulesApi.getAllRules() + val response = executeRequest(globalErrorReceiver) { + pushRulesApi.getAllRules() } savePushRulesTask.execute(SavePushRulesTask.Params(response)) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersTask.kt index 125c8f0022..ba413a34db 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersTask.kt @@ -36,8 +36,8 @@ internal class DefaultGetPushersTask @Inject constructor( ) : GetPushersTask { override suspend fun execute(params: Unit) { - val response = executeRequest(globalErrorReceiver) { - apiCall = pushersAPI.getPushers() + val response = executeRequest(globalErrorReceiver) { + pushersAPI.getPushers() } monarchy.awaitTransaction { realm -> // clear existings? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt index cbcb7d2b37..daf9397ce8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.pushers import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse import org.matrix.android.sdk.api.pushrules.rest.PushRule import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET @@ -30,7 +29,7 @@ internal interface PushRulesApi { * Get all push rules */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/") - fun getAllRules(): Call + suspend fun getAllRules(): GetPushRulesResponse /** * Update the ruleID enable status @@ -40,10 +39,9 @@ internal interface PushRulesApi { * @param enable the new enable status */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/enabled") - fun updateEnableRuleStatus(@Path("kind") kind: String, - @Path("ruleId") ruleId: String, - @Body enable: Boolean?) - : Call + suspend fun updateEnableRuleStatus(@Path("kind") kind: String, + @Path("ruleId") ruleId: String, + @Body enable: Boolean?) /** * Update the ruleID action @@ -54,10 +52,9 @@ internal interface PushRulesApi { * @param actions the actions */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/actions") - fun updateRuleActions(@Path("kind") kind: String, - @Path("ruleId") ruleId: String, - @Body actions: Any) - : Call + suspend fun updateRuleActions(@Path("kind") kind: String, + @Path("ruleId") ruleId: String, + @Body actions: Any) /** * Delete a rule @@ -66,9 +63,8 @@ internal interface PushRulesApi { * @param ruleId the ruleId */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}") - fun deleteRule(@Path("kind") kind: String, - @Path("ruleId") ruleId: String) - : Call + suspend fun deleteRule(@Path("kind") kind: String, + @Path("ruleId") ruleId: String) /** * Add the ruleID enable status @@ -78,8 +74,7 @@ internal interface PushRulesApi { * @param rule the rule to add. */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}") - fun addRule(@Path("kind") kind: String, - @Path("ruleId") ruleId: String, - @Body rule: PushRule) - : Call + suspend fun addRule(@Path("kind") kind: String, + @Path("ruleId") ruleId: String, + @Body rule: PushRule) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersAPI.kt index ed4fb73e1b..0afea6996d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersAPI.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.session.pushers import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST @@ -29,7 +28,7 @@ internal interface PushersAPI { * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushers */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushers") - fun getPushers(): Call + suspend fun getPushers(): GetPushersResponse /** * This endpoint allows the creation, modification and deletion of pushers for this user ID. @@ -38,5 +37,5 @@ internal interface PushersAPI { * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushers/set") - fun setPusher(@Body jsonPusher: JsonPusher): Call + suspend fun setPusher(@Body jsonPusher: JsonPusher) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt index ff3122f566..23d0515f41 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt @@ -36,7 +36,7 @@ internal class DefaultRemovePushRuleTask @Inject constructor( override suspend fun execute(params: RemovePushRuleTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = pushRulesApi.deleteRule(params.kind.value, params.pushRule.ruleId) + pushRulesApi.deleteRule(params.kind.value, params.pushRule.ruleId) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt index e3f4fdb789..3a2ebf40c2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt @@ -62,8 +62,8 @@ internal class DefaultRemovePusherTask @Inject constructor( data = JsonPusherData(existing.data.url, existing.data.format), append = false ) - executeRequest(globalErrorReceiver) { - apiCall = pushersAPI.setPusher(deleteBody) + executeRequest(globalErrorReceiver) { + pushersAPI.setPusher(deleteBody) } monarchy.awaitTransaction { PusherEntity.where(it, params.pushKey).findFirst()?.deleteFromRealm() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt index a5c220e662..2a24aee892 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt @@ -38,8 +38,8 @@ internal class DefaultUpdatePushRuleActionsTask @Inject constructor( override suspend fun execute(params: UpdatePushRuleActionsTask.Params) { if (params.oldPushRule.enabled != params.newPushRule.enabled) { // First change enabled state - executeRequest(globalErrorReceiver) { - apiCall = pushRulesApi.updateEnableRuleStatus(params.kind.value, params.newPushRule.ruleId, params.newPushRule.enabled) + executeRequest(globalErrorReceiver) { + pushRulesApi.updateEnableRuleStatus(params.kind.value, params.newPushRule.ruleId, params.newPushRule.enabled) } } @@ -47,8 +47,8 @@ internal class DefaultUpdatePushRuleActionsTask @Inject constructor( // Also ensure the actions are up to date val body = mapOf("actions" to params.newPushRule.actions) - executeRequest(globalErrorReceiver) { - apiCall = pushRulesApi.updateRuleActions(params.kind.value, params.newPushRule.ruleId, body) + executeRequest(globalErrorReceiver) { + pushRulesApi.updateRuleActions(params.kind.value, params.newPushRule.ruleId, body) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt index f36b5c55fb..9d7a46bede 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt @@ -35,7 +35,7 @@ internal class DefaultUpdatePushRuleEnableStatusTask @Inject constructor( override suspend fun execute(params: UpdatePushRuleEnableStatusTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = pushRulesApi.updateEnableRuleStatus(params.kind.value, params.pushRule.ruleId, params.enabled) + pushRulesApi.updateEnableRuleStatus(params.kind.value, params.pushRule.ruleId, params.enabled) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayAPI.kt index d95587fc22..4333d6c7b8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayAPI.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.session.pushers.gateway import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.POST @@ -27,5 +26,5 @@ internal interface PushGatewayAPI { * Ref: https://matrix.org/docs/spec/push_gateway/r0.1.1#post-matrix-push-v1-notify */ @POST(NetworkConstants.URI_PUSH_GATEWAY_PREFIX_PATH + "notify") - fun notify(@Body body: PushGatewayNotifyBody): Call + suspend fun notify(@Body body: PushGatewayNotifyBody): PushGatewayNotifyResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyTask.kt index df6f46fa81..316e221b32 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyTask.kt @@ -45,8 +45,8 @@ internal class DefaultPushGatewayNotifyTask @Inject constructor( ) .create(PushGatewayAPI::class.java) - val response = executeRequest(null) { - apiCall = sygnalApi.notify( + val response = executeRequest(null) { + sygnalApi.notify( PushGatewayNotifyBody( PushGatewayNotification( eventId = params.eventId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index f510b3c997..22f61bc517 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -18,16 +18,19 @@ package org.matrix.android.sdk.internal.session.room import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations +import androidx.paging.PagedList import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.RoomService import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams +import org.matrix.android.sdk.api.session.room.UpdatableFilterLivePageResult import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.peeking.PeekResult +import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.database.mapper.asDomain @@ -87,6 +90,20 @@ internal class DefaultRoomService @Inject constructor( return roomSummaryDataSource.getRoomSummariesLive(queryParams) } + override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config) + : LiveData> { + return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams, pagedListConfig) + } + + override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config) + : UpdatableFilterLivePageResult { + return roomSummaryDataSource.getFilteredPagedRoomSummariesLive(queryParams, pagedListConfig) + } + + override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount { + return roomSummaryDataSource.getNotificationCountForRooms(queryParams) + } + override fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List { return roomSummaryDataSource.getBreadcrumbs(queryParams) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index b065a30fc9..6fee630510 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt @@ -38,7 +38,6 @@ import org.matrix.android.sdk.internal.session.room.tags.TagBody import org.matrix.android.sdk.internal.session.room.timeline.EventContextResponse import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse import org.matrix.android.sdk.internal.session.room.typing.TypingBody -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET @@ -57,9 +56,9 @@ internal interface RoomAPI { * Ref: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-publicrooms */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "publicRooms") - fun publicRooms(@Query("server") server: String?, - @Body publicRoomsParams: PublicRoomsParams - ): Call + suspend fun publicRooms(@Query("server") server: String?, + @Body publicRoomsParams: PublicRoomsParams + ): PublicRoomsResponse /** * Create a room. @@ -71,7 +70,7 @@ internal interface RoomAPI { */ @Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "createRoom") - fun createRoom(@Body param: CreateRoomBody): Call + suspend fun createRoom(@Body param: CreateRoomBody): CreateRoomResponse /** * Get a list of messages starting from a reference. @@ -83,12 +82,12 @@ internal interface RoomAPI { * @param filter A JSON RoomEventFilter to filter returned events with. Optional. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/messages") - fun getRoomMessagesFrom(@Path("roomId") roomId: String, - @Query("from") from: String, - @Query("dir") dir: String, - @Query("limit") limit: Int, - @Query("filter") filter: String? - ): Call + suspend fun getRoomMessagesFrom(@Path("roomId") roomId: String, + @Query("from") from: String, + @Query("dir") dir: String, + @Query("limit") limit: Int, + @Query("filter") filter: String? + ): PaginationResponse /** * Get all members of a room @@ -99,11 +98,11 @@ internal interface RoomAPI { * @param notMembership to exclude one type of membership (optional) */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/members") - fun getMembers(@Path("roomId") roomId: String, - @Query("at") syncToken: String?, - @Query("membership") membership: Membership?, - @Query("not_membership") notMembership: Membership? - ): Call + suspend fun getMembers(@Path("roomId") roomId: String, + @Query("at") syncToken: String?, + @Query("membership") membership: Membership?, + @Query("not_membership") notMembership: Membership? + ): RoomMembersResponse /** * Send an event to a room. @@ -114,11 +113,11 @@ internal interface RoomAPI { * @param content the event content */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/send/{eventType}/{txId}") - fun send(@Path("txId") txId: String, - @Path("roomId") roomId: String, - @Path("eventType") eventType: String, - @Body content: Content? - ): Call + suspend fun send(@Path("txId") txId: String, + @Path("roomId") roomId: String, + @Path("eventType") eventType: String, + @Body content: Content? + ): SendResponse /** * Get the context surrounding an event. @@ -129,10 +128,10 @@ internal interface RoomAPI { * @param filter A JSON RoomEventFilter to filter returned events with. Optional. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/context/{eventId}") - fun getContextOfEvent(@Path("roomId") roomId: String, - @Path("eventId") eventId: String, - @Query("limit") limit: Int, - @Query("filter") filter: String? = null): Call + suspend fun getContextOfEvent(@Path("roomId") roomId: String, + @Path("eventId") eventId: String, + @Query("limit") limit: Int, + @Query("filter") filter: String? = null): EventContextResponse /** * Retrieve an event from its room id / events id @@ -141,8 +140,8 @@ internal interface RoomAPI { * @param eventId the event Id */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/event/{eventId}") - fun getEvent(@Path("roomId") roomId: String, - @Path("eventId") eventId: String): Call + suspend fun getEvent(@Path("roomId") roomId: String, + @Path("eventId") eventId: String): Event /** * Send read markers. @@ -151,8 +150,16 @@ internal interface RoomAPI { * @param markers the read markers */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/read_markers") - fun sendReadMarker(@Path("roomId") roomId: String, - @Body markers: Map): Call + suspend fun sendReadMarker(@Path("roomId") roomId: String, + @Body markers: Map) + + /** + * Send receipt to a room + */ + @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/receipt/{receiptType}/{eventId}") + suspend fun sendReceipt(@Path("roomId") roomId: String, + @Path("receiptType") receiptType: String, + @Path("eventId") eventId: String) /** * Invite a user to the given room. @@ -162,8 +169,8 @@ internal interface RoomAPI { * @param body a object that just contains a user id */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite") - fun invite(@Path("roomId") roomId: String, - @Body body: InviteBody): Call + suspend fun invite(@Path("roomId") roomId: String, + @Body body: InviteBody) /** * Invite a user to a room, using a ThreePid @@ -171,8 +178,8 @@ internal interface RoomAPI { * @param roomId Required. The room identifier (not alias) to which to invite the user. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite") - fun invite3pid(@Path("roomId") roomId: String, - @Body body: ThreePidInviteBody): Call + suspend fun invite3pid(@Path("roomId") roomId: String, + @Body body: ThreePidInviteBody) /** * Send a generic state event @@ -182,9 +189,9 @@ internal interface RoomAPI { * @param params the request parameters */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}") - fun sendStateEvent(@Path("roomId") roomId: String, - @Path("state_event_type") stateEventType: String, - @Body params: JsonDict): Call + suspend fun sendStateEvent(@Path("roomId") roomId: String, + @Path("state_event_type") stateEventType: String, + @Body params: JsonDict) /** * Send a generic state event @@ -195,17 +202,17 @@ internal interface RoomAPI { * @param params the request parameters */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}/{state_key}") - fun sendStateEvent(@Path("roomId") roomId: String, - @Path("state_event_type") stateEventType: String, - @Path("state_key") stateKey: String, - @Body params: JsonDict): Call + suspend fun sendStateEvent(@Path("roomId") roomId: String, + @Path("state_event_type") stateEventType: String, + @Path("state_key") stateKey: String, + @Body params: JsonDict) /** * Get state events of a room * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-state */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state") - fun getRoomState(@Path("roomId") roomId: String) : Call> + suspend fun getRoomState(@Path("roomId") roomId: String): List /** * Send a relation event to a room. @@ -216,12 +223,12 @@ internal interface RoomAPI { * @param content the event content */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/send_relation/{parent_id}/{relation_type}/{event_type}") - fun sendRelation(@Path("roomId") roomId: String, - @Path("parent_id") parentId: String, - @Path("relation_type") relationType: String, - @Path("event_type") eventType: String, - @Body content: Content? - ): Call + suspend fun sendRelation(@Path("roomId") roomId: String, + @Path("parent_id") parentId: String, + @Path("relation_type") relationType: String, + @Path("event_type") eventType: String, + @Body content: Content? + ): SendResponse /** * Paginate relations for event based in normal topological order @@ -230,11 +237,11 @@ internal interface RoomAPI { * @param eventType filter for this event type */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/relations/{eventId}/{relationType}/{eventType}") - fun getRelations(@Path("roomId") roomId: String, - @Path("eventId") eventId: String, - @Path("relationType") relationType: String, - @Path("eventType") eventType: String - ): Call + suspend fun getRelations(@Path("roomId") roomId: String, + @Path("eventId") eventId: String, + @Path("relationType") relationType: String, + @Path("eventType") eventType: String + ): RelationsResponse /** * Join the given room. @@ -244,9 +251,9 @@ internal interface RoomAPI { * @param params the request body */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "join/{roomIdOrAlias}") - fun join(@Path("roomIdOrAlias") roomIdOrAlias: String, - @Query("server_name") viaServers: List, - @Body params: Map): Call + suspend fun join(@Path("roomIdOrAlias") roomIdOrAlias: String, + @Query("server_name") viaServers: List, + @Body params: Map): JoinRoomResponse /** * Leave the given room. @@ -255,8 +262,8 @@ internal interface RoomAPI { * @param params the request body */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/leave") - fun leave(@Path("roomId") roomId: String, - @Body params: Map): Call + suspend fun leave(@Path("roomId") roomId: String, + @Body params: Map) /** * Ban a user from the given room. @@ -265,8 +272,8 @@ internal interface RoomAPI { * @param userIdAndReason the banned user object (userId and reason for ban) */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/ban") - fun ban(@Path("roomId") roomId: String, - @Body userIdAndReason: UserIdAndReason): Call + suspend fun ban(@Path("roomId") roomId: String, + @Body userIdAndReason: UserIdAndReason) /** * unban a user from the given room. @@ -275,8 +282,8 @@ internal interface RoomAPI { * @param userIdAndReason the unbanned user object (userId and reason for unban) */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/unban") - fun unban(@Path("roomId") roomId: String, - @Body userIdAndReason: UserIdAndReason): Call + suspend fun unban(@Path("roomId") roomId: String, + @Body userIdAndReason: UserIdAndReason) /** * Kick a user from the given room. @@ -285,8 +292,8 @@ internal interface RoomAPI { * @param userIdAndReason the kicked user object (userId and reason for kicking) */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/kick") - fun kick(@Path("roomId") roomId: String, - @Body userIdAndReason: UserIdAndReason): Call + suspend fun kick(@Path("roomId") roomId: String, + @Body userIdAndReason: UserIdAndReason) /** * Strips all information out of an event which isn't critical to the integrity of the server-side representation of the room. @@ -299,12 +306,12 @@ internal interface RoomAPI { * @param reason json containing reason key {"reason": "Indecent material"} */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/redact/{eventId}/{txnId}") - fun redactEvent( + suspend fun redactEvent( @Path("txnId") txId: String, @Path("roomId") roomId: String, @Path("eventId") eventId: String, @Body reason: Map - ): Call + ): SendResponse /** * Reports an event as inappropriate to the server, which may then notify the appropriate people. @@ -314,24 +321,24 @@ internal interface RoomAPI { * @param body body containing score and reason */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/report/{eventId}") - fun reportContent(@Path("roomId") roomId: String, - @Path("eventId") eventId: String, - @Body body: ReportContentBody): Call + suspend fun reportContent(@Path("roomId") roomId: String, + @Path("eventId") eventId: String, + @Body body: ReportContentBody) /** * Get a list of aliases maintained by the local server for the given room. * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-aliases */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2432/rooms/{roomId}/aliases") - fun getAliases(@Path("roomId") roomId: String): Call + suspend fun getAliases(@Path("roomId") roomId: String): GetAliasesResponse /** * Inform that the user is starting to type or has stopped typing */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/typing/{userId}") - fun sendTypingState(@Path("roomId") roomId: String, - @Path("userId") userId: String, - @Body body: TypingBody): Call + suspend fun sendTypingState(@Path("roomId") roomId: String, + @Path("userId") userId: String, + @Body body: TypingBody) /** * Room tagging @@ -341,16 +348,16 @@ internal interface RoomAPI { * Add a tag to a room. */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}") - fun putTag(@Path("userId") userId: String, - @Path("roomId") roomId: String, - @Path("tag") tag: String, - @Body body: TagBody): Call + suspend fun putTag(@Path("userId") userId: String, + @Path("roomId") roomId: String, + @Path("tag") tag: String, + @Body body: TagBody) /** * Delete a tag from a room. */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}") - fun deleteTag(@Path("userId") userId: String, - @Path("roomId") roomId: String, - @Path("tag") tag: String): Call + suspend fun deleteTag(@Path("userId") userId: String, + @Path("roomId") roomId: String, + @Path("tag") tag: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt index 9bcb1eb196..60ad83ee05 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt @@ -17,10 +17,11 @@ package org.matrix.android.sdk.internal.session.room import io.realm.Realm +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent -import org.matrix.android.sdk.internal.database.mapper.ContentMapper +import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity @@ -39,24 +40,35 @@ internal class RoomAvatarResolver @Inject constructor(@UserId private val userId * @return the room avatar url, can be a fallback to a room member avatar or null */ fun resolve(realm: Realm, roomId: String): String? { - var res: String? - val roomName = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_AVATAR, stateKey = "")?.root - res = ContentMapper.map(roomName?.content).toModel()?.avatarUrl - if (!res.isNullOrEmpty()) { - return res + val roomName = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_AVATAR, stateKey = "") + ?.root + ?.asDomain() + ?.content + ?.toModel() + ?.avatarUrl + if (!roomName.isNullOrEmpty()) { + return roomName } val roomMembers = RoomMemberHelper(realm, roomId) val members = roomMembers.queryActiveRoomMembersEvent().findAll() // detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat) - val isDirectRoom = RoomSummaryEntity.where(realm, roomId).findFirst()?.isDirect ?: false + val isDirectRoom = RoomSummaryEntity.where(realm, roomId).findFirst()?.isDirect.orFalse() + if (isDirectRoom) { if (members.size == 1) { - res = members.firstOrNull()?.avatarUrl + // Use avatar of a left user + val firstLeftAvatarUrl = roomMembers.queryLeftRoomMembersEvent() + .findAll() + .firstOrNull { !it.avatarUrl.isNullOrEmpty() } + ?.avatarUrl + + return firstLeftAvatarUrl ?: members.firstOrNull()?.avatarUrl } else if (members.size == 2) { val firstOtherMember = members.where().notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId).findFirst() - res = firstOtherMember?.avatarUrl + return firstOtherMember?.avatarUrl } } - return res + + return null } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index 66b7272360..5133f72932 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -79,9 +79,11 @@ import org.matrix.android.sdk.internal.session.room.tags.DefaultDeleteTagFromRoo import org.matrix.android.sdk.internal.session.room.tags.DeleteTagFromRoomTask import org.matrix.android.sdk.internal.session.room.timeline.DefaultFetchTokenAndPaginateTask import org.matrix.android.sdk.internal.session.room.timeline.DefaultGetContextOfEventTask +import org.matrix.android.sdk.internal.session.room.timeline.DefaultGetEventTask import org.matrix.android.sdk.internal.session.room.timeline.DefaultPaginationTask import org.matrix.android.sdk.internal.session.room.timeline.FetchTokenAndPaginateTask import org.matrix.android.sdk.internal.session.room.timeline.GetContextOfEventTask +import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask import org.matrix.android.sdk.internal.session.room.timeline.PaginationTask import org.matrix.android.sdk.internal.session.room.typing.DefaultSendTypingTask import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask @@ -228,4 +230,7 @@ internal abstract class RoomModule { @Binds abstract fun bindPeekRoomTask(task: DefaultPeekRoomTask): PeekRoomTask + + @Binds + abstract fun bindGetEventTask(task: DefaultGetEventTask): GetEventTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt index 9e4ec6f777..97ea1d6ad1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt @@ -45,8 +45,8 @@ internal class DefaultAddRoomAliasTask @Inject constructor( override suspend fun execute(params: AddRoomAliasTask.Params) { aliasAvailabilityChecker.check(params.aliasLocalPart) - executeRequest(globalErrorReceiver) { - apiCall = directoryAPI.addRoomAlias( + executeRequest(globalErrorReceiver) { + directoryAPI.addRoomAlias( roomAlias = params.aliasLocalPart.toFullLocalAlias(userId), body = AddRoomAliasBody( roomId = params.roomId diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt index 6ad3db90a9..01ac3fcec8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt @@ -34,8 +34,8 @@ internal class DefaultDeleteRoomAliasTask @Inject constructor( ) : DeleteRoomAliasTask { override suspend fun execute(params: DeleteRoomAliasTask.Params) { - executeRequest(globalErrorReceiver) { - apiCall = directoryAPI.deleteRoomAlias( + executeRequest(globalErrorReceiver) { + directoryAPI.deleteRoomAlias( roomAlias = params.roomAlias ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt index a53ffc4fcd..71c8c9cd38 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt @@ -52,8 +52,8 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor( Optional.from(null) } else { val description = tryOrNull("## Failed to get roomId from alias") { - executeRequest(globalErrorReceiver) { - apiCall = directoryAPI.getRoomIdByAlias(params.roomAlias) + executeRequest(globalErrorReceiver) { + directoryAPI.getRoomIdByAlias(params.roomAlias) } } Optional.from(description) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt index 202cb1f6de..1ff4156ed3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt @@ -35,8 +35,8 @@ internal class DefaultGetRoomLocalAliasesTask @Inject constructor( override suspend fun execute(params: GetRoomLocalAliasesTask.Params): List { // We do not check for "org.matrix.msc2432", so the API may be missing - val response = executeRequest(globalErrorReceiver) { - apiCall = roomAPI.getAliases(roomId = params.roomId) + val response = executeRequest(globalErrorReceiver) { + roomAPI.getAliases(roomId = params.roomId) } return response.aliases diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt index 51a849a35e..9faf50dd8b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt @@ -41,8 +41,8 @@ internal class RoomAliasAvailabilityChecker @Inject constructor( // Check alias availability val fullAlias = aliasLocalPart.toFullLocalAlias(userId) try { - executeRequest(globalErrorReceiver) { - apiCall = directoryAPI.getRoomIdByAlias(fullAlias) + executeRequest(globalErrorReceiver) { + directoryAPI.getRoomIdByAlias(fullAlias) } } catch (throwable: Throwable) { if (throwable is Failure.ServerError && throwable.httpCode == 404) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt index 9c16bd1b0f..bafe2b90ae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt @@ -17,18 +17,19 @@ package org.matrix.android.sdk.internal.session.room.create import com.zhuinden.monarchy.Monarchy +import io.realm.Realm import io.realm.RealmConfiguration import kotlinx.coroutines.TimeoutCancellationException import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure +import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.internal.database.awaitNotEmptyResult -import org.matrix.android.sdk.internal.database.model.RoomEntity -import org.matrix.android.sdk.internal.database.model.RoomEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.network.GlobalErrorReceiver @@ -75,8 +76,8 @@ internal class DefaultCreateRoomTask @Inject constructor( val createRoomBody = createRoomBodyBuilder.build(params) val createRoomResponse = try { - executeRequest(globalErrorReceiver) { - apiCall = roomAPI.createRoom(createRoomBody) + executeRequest(globalErrorReceiver) { + roomAPI.createRoom(createRoomBody) } } catch (throwable: Throwable) { if (throwable is Failure.ServerError) { @@ -96,12 +97,18 @@ internal class DefaultCreateRoomTask @Inject constructor( // Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before) try { awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> - realm.where(RoomEntity::class.java) - .equalTo(RoomEntityFields.ROOM_ID, roomId) + realm.where(RoomSummaryEntity::class.java) + .equalTo(RoomSummaryEntityFields.ROOM_ID, roomId) + .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name) } } catch (exception: TimeoutCancellationException) { throw CreateRoomFailure.CreatedWithTimeout } + + Realm.getInstance(realmConfiguration).executeTransactionAsync { + RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis() + } + if (otherUserId != null) { handleDirectChatCreation(roomId, otherUserId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetPublicRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetPublicRoomTask.kt index edd8ae9b0d..4a6b0703c5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetPublicRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetPublicRoomTask.kt @@ -38,7 +38,7 @@ internal class DefaultGetPublicRoomTask @Inject constructor( override suspend fun execute(params: GetPublicRoomTask.Params): PublicRoomsResponse { return executeRequest(globalErrorReceiver) { - apiCall = roomAPI.publicRooms(params.server, params.publicRoomsParams) + roomAPI.publicRooms(params.server, params.publicRoomsParams) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetRoomDirectoryVisibilityTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetRoomDirectoryVisibilityTask.kt index 8d71001ef9..77492e429f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetRoomDirectoryVisibilityTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetRoomDirectoryVisibilityTask.kt @@ -20,7 +20,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.directory.DirectoryAPI -import org.matrix.android.sdk.internal.session.directory.RoomDirectoryVisibilityJson import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -36,8 +35,8 @@ internal class DefaultGetRoomDirectoryVisibilityTask @Inject constructor( ) : GetRoomDirectoryVisibilityTask { override suspend fun execute(params: GetRoomDirectoryVisibilityTask.Params): RoomDirectoryVisibility { - return executeRequest(globalErrorReceiver) { - apiCall = directoryAPI.getRoomDirectoryVisibility(params.roomId) + return executeRequest(globalErrorReceiver) { + directoryAPI.getRoomDirectoryVisibility(params.roomId) } .visibility } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt index cbb0b6d5d1..f46d06bd5c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt @@ -37,8 +37,8 @@ internal class DefaultSetRoomDirectoryVisibilityTask @Inject constructor( ) : SetRoomDirectoryVisibilityTask { override suspend fun execute(params: SetRoomDirectoryVisibilityTask.Params) { - executeRequest(globalErrorReceiver) { - apiCall = directoryAPI.setRoomDirectoryVisibility( + executeRequest(globalErrorReceiver) { + directoryAPI.setRoomDirectoryVisibility( params.roomId, RoomDirectoryVisibilityJson(visibility = params.roomDirectoryVisibility) ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt index 6adf3c59d1..3d0f51b831 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt @@ -90,8 +90,8 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( val lastToken = syncTokenStore.getLastToken() val response = try { - executeRequest(globalErrorReceiver) { - apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership) + executeRequest(globalErrorReceiver) { + roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership) } } catch (throwable: Throwable) { // Revert status to NONE diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt index 0e18e30b13..3aa812d93d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.membership import io.realm.Realm import org.matrix.android.sdk.api.MatrixConfiguration +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.Membership @@ -51,14 +52,14 @@ internal class RoomDisplayNameResolver @Inject constructor( * @param roomId: the roomId to resolve the name of. * @return the room display name */ - fun resolve(realm: Realm, roomId: String): CharSequence { + fun resolve(realm: Realm, roomId: String): String { // this algorithm is the one defined in // https://github.com/matrix-org/matrix-js-sdk/blob/develop/lib/models/room.js#L617 // calculateRoomName(room, userId) // For Lazy Loaded room, see algorithm here: // https://docs.google.com/document/d/11i14UI1cUz-OJ0knD5BFu7fmT6Fo327zvMYqfSAR7xs/edit#heading=h.qif6pkqyjgzn - var name: CharSequence? + var name: String? val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() val roomName = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_NAME, stateKey = "")?.root name = ContentMapper.map(roomName?.content).toModel()?.name @@ -77,14 +78,14 @@ internal class RoomDisplayNameResolver @Inject constructor( if (roomEntity?.membership == Membership.INVITE) { val inviteMeEvent = roomMembers.getLastStateEvent(userId) val inviterId = inviteMeEvent?.sender - name = if (inviterId != null) { - activeMembers.where() - .equalTo(RoomMemberSummaryEntityFields.USER_ID, inviterId) - .findFirst() - ?.displayName - } else { - roomDisplayNameFallbackProvider.getNameForRoomInvite() - } + name = inviterId + ?.let { + activeMembers.where() + .equalTo(RoomMemberSummaryEntityFields.USER_ID, it) + .findFirst() + ?.getBestName() + } + ?: roomDisplayNameFallbackProvider.getNameForRoomInvite() } else if (roomEntity?.membership == Membership.JOIN) { val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() val invitedCount = roomSummary?.invitedMembersCount ?: 0 @@ -105,10 +106,17 @@ internal class RoomDisplayNameResolver @Inject constructor( val otherMembersCount = otherMembersSubset.count() name = when (otherMembersCount) { 0 -> { - roomDisplayNameFallbackProvider.getNameForEmptyRoom() - // TODO (was xx and yyy) ... + // Get left members if any + val leftMembersNames = roomMembers.queryLeftRoomMembersEvent() + .findAll() + .map { it.getBestName() } + roomDisplayNameFallbackProvider.getNameForEmptyRoom(roomSummary?.isDirect.orFalse(), leftMembersNames) + } + 1 -> { + roomDisplayNameFallbackProvider.getNameFor1member( + resolveRoomMemberName(otherMembersSubset[0], roomMembers) + ) } - 1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers) 2 -> { roomDisplayNameFallbackProvider.getNameFor2members( resolveRoomMemberName(otherMembersSubset[0], roomMembers), @@ -145,12 +153,11 @@ internal class RoomDisplayNameResolver @Inject constructor( } /** See [org.matrix.android.sdk.api.session.room.sender.SenderInfo.disambiguatedDisplayName] */ - private fun resolveRoomMemberName(roomMemberSummary: RoomMemberSummaryEntity?, - roomMemberHelper: RoomMemberHelper): String? { - if (roomMemberSummary == null) return null + private fun resolveRoomMemberName(roomMemberSummary: RoomMemberSummaryEntity, + roomMemberHelper: RoomMemberHelper): String { val isUnique = roomMemberHelper.isUniqueDisplayName(roomMemberSummary.displayName) return if (isUnique) { - roomMemberSummary.displayName + roomMemberSummary.getBestName() } else { "${roomMemberSummary.displayName} (${roomMemberSummary.userId})" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt index 89fe2901c0..2ecacf335b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt @@ -16,12 +16,12 @@ package org.matrix.android.sdk.internal.session.room.membership +import io.realm.Realm import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent import org.matrix.android.sdk.internal.session.user.UserEntityFactory -import io.realm.Realm import javax.inject.Inject internal class RoomMemberEventHandler @Inject constructor() { @@ -31,7 +31,7 @@ internal class RoomMemberEventHandler @Inject constructor() { return false } val userId = event.stateKey ?: return false - val roomMember = event.content.toModel() + val roomMember = event.getFixedRoomMemberContent() return handle(realm, roomId, userId, roomMember) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt index 2a7c46bd42..9ce8db25a5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt @@ -75,6 +75,11 @@ internal class RoomMemberHelper(private val realm: Realm, .equalTo(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, Membership.INVITE.name) } + fun queryLeftRoomMembersEvent(): RealmQuery { + return queryRoomMembersEvent() + .equalTo(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, Membership.LEAVE.name) + } + fun queryActiveRoomMembersEvent(): RealmQuery { return queryRoomMembersEvent() .beginGroup() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/admin/MembershipAdminTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/admin/MembershipAdminTask.kt index 4654a28536..d2c21f3520 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/admin/MembershipAdminTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/admin/MembershipAdminTask.kt @@ -41,8 +41,8 @@ internal class DefaultMembershipAdminTask @Inject constructor(private val roomAP override suspend fun execute(params: MembershipAdminTask.Params) { val userIdAndReason = UserIdAndReason(params.userId, params.reason) - executeRequest(null) { - apiCall = when (params.type) { + executeRequest(null) { + when (params.type) { MembershipAdminTask.Type.BAN -> roomAPI.ban(params.roomId, userIdAndReason) MembershipAdminTask.Type.UNBAN -> roomAPI.unban(params.roomId, userIdAndReason) MembershipAdminTask.Type.KICK -> roomAPI.kick(params.roomId, userIdAndReason) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteTask.kt index 05503bd643..7e7b80baaf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteTask.kt @@ -36,11 +36,13 @@ internal class DefaultInviteTask @Inject constructor( ) : InviteTask { override suspend fun execute(params: InviteTask.Params) { - return executeRequest(globalErrorReceiver) { - val body = InviteBody(params.userId, params.reason) - apiCall = roomAPI.invite(params.roomId, body) - isRetryable = true - maxRetryCount = 3 + val body = InviteBody(params.userId, params.reason) + return executeRequest( + globalErrorReceiver, + canRetry = true, + maxRetriesCount = 3 + ) { + roomAPI.invite(params.roomId, body) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt index 3b7639d42f..33776e4f6e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt @@ -16,21 +16,23 @@ package org.matrix.android.sdk.internal.session.room.membership.joining +import io.realm.Realm +import io.realm.RealmConfiguration +import kotlinx.coroutines.TimeoutCancellationException import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.internal.database.awaitNotEmptyResult -import org.matrix.android.sdk.internal.database.model.RoomEntity -import org.matrix.android.sdk.internal.database.model.RoomEntityFields +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI -import org.matrix.android.sdk.internal.session.room.create.JoinRoomResponse import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask import org.matrix.android.sdk.internal.task.Task -import io.realm.RealmConfiguration -import kotlinx.coroutines.TimeoutCancellationException -import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -54,8 +56,8 @@ internal class DefaultJoinRoomTask @Inject constructor( override suspend fun execute(params: JoinRoomTask.Params) { roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining) val joinRoomResponse = try { - executeRequest(globalErrorReceiver) { - apiCall = roomAPI.join( + executeRequest(globalErrorReceiver) { + roomAPI.join( roomIdOrAlias = params.roomIdOrAlias, viaServers = params.viaServers.take(3), params = mapOf("reason" to params.reason) @@ -69,12 +71,18 @@ internal class DefaultJoinRoomTask @Inject constructor( val roomId = joinRoomResponse.roomId try { awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> - realm.where(RoomEntity::class.java) - .equalTo(RoomEntityFields.ROOM_ID, roomId) + realm.where(RoomSummaryEntity::class.java) + .equalTo(RoomSummaryEntityFields.ROOM_ID, roomId) + .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name) } } catch (exception: TimeoutCancellationException) { throw JoinRoomFailure.JoinedWithTimeout } + + Realm.getInstance(realmConfiguration).executeTransactionAsync { + RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis() + } + setReadMarkers(roomId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt index 37bb7570d1..1b836e36a6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt @@ -68,8 +68,8 @@ internal class DefaultLeaveRoomTask @Inject constructor( leaveRoom(predecessorRoomId, reason) } try { - executeRequest(globalErrorReceiver) { - apiCall = roomAPI.leave(roomId, mapOf("reason" to reason)) + executeRequest(globalErrorReceiver) { + roomAPI.leave(roomId, mapOf("reason" to reason)) } } catch (failure: Throwable) { roomChangeMembershipStateDataSource.updateState(roomId, ChangeMembershipState.FailedLeaving(failure)) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/InviteThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/InviteThreePidTask.kt index d237ec795e..fa0a2d608a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/InviteThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/InviteThreePidTask.kt @@ -59,7 +59,7 @@ internal class DefaultInviteThreePidTask @Inject constructor( medium = params.threePid.toMedium(), address = params.threePid.value ) - apiCall = roomAPI.invite3pid(params.roomId, body) + roomAPI.invite3pid(params.roomId, body) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt index dbec6b555c..64cbef23ec 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt @@ -36,7 +36,7 @@ internal class DefaultResolveRoomStateTask @Inject constructor( override suspend fun execute(params: ResolveRoomStateTask.Params): List { return executeRequest(globalErrorReceiver) { - apiCall = roomAPI.getRoomState(params.roomId) + roomAPI.getRoomState(params.roomId) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt index 54d2307dd4..e4147d55b8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt @@ -62,7 +62,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor( ) : SetReadMarkersTask { override suspend fun execute(params: SetReadMarkersTask.Params) { - val markers = HashMap() + val markers = mutableMapOf() Timber.v("Execute set read marker with params: $params") val latestSyncedEventId = latestSyncedEventId(params.roomId) val fullyReadEventId = if (params.forceReadMarker) { @@ -96,9 +96,18 @@ internal class DefaultSetReadMarkersTask @Inject constructor( updateDatabase(params.roomId, markers, shouldUpdateRoomSummary) } if (markers.isNotEmpty()) { - executeRequest(globalErrorReceiver) { - isRetryable = true - apiCall = roomAPI.sendReadMarker(params.roomId, markers) + executeRequest( + globalErrorReceiver, + canRetry = true + ) { + if (markers[READ_MARKER] == null) { + if (readReceiptEventId != null) { + roomAPI.sendReceipt(params.roomId, READ_RECEIPT, readReceiptEventId) + } + } else { + // "m.fully_read" value is mandatory to make this call + roomAPI.sendReadMarker(params.roomId, markers) + } } } } @@ -108,7 +117,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor( TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId } - private suspend fun updateDatabase(roomId: String, markers: HashMap, shouldUpdateRoomSummary: Boolean) { + private suspend fun updateDatabase(roomId: String, markers: Map, shouldUpdateRoomSummary: Boolean) { monarchy.awaitTransaction { realm -> val readMarkerId = markers[READ_MARKER] val readReceiptId = markers[READ_RECEIPT] diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt index f9fd5f9348..5f5c000171 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt @@ -40,8 +40,8 @@ internal class DefaultFetchEditHistoryTask @Inject constructor( override suspend fun execute(params: FetchEditHistoryTask.Params): List { val isRoomEncrypted = cryptoSessionInfoProvider.isRoomEncrypted(params.roomId) - val response = executeRequest(globalErrorReceiver) { - apiCall = roomAPI.getRelations( + val response = executeRequest(globalErrorReceiver) { + roomAPI.getRelations( roomId = params.roomId, eventId = params.eventId, relationType = RelationType.REPLACE, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/SendRelationWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/SendRelationWorker.kt index 403aa274fe..5d0879d706 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/SendRelationWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/SendRelationWorker.kt @@ -28,7 +28,6 @@ import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository -import org.matrix.android.sdk.internal.session.room.send.SendResponse import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams import javax.inject.Inject @@ -84,8 +83,8 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters) } private suspend fun sendRelation(roomId: String, relationType: String, relatedEventId: String, localEvent: Event) { - executeRequest(globalErrorReceiver) { - apiCall = roomAPI.sendRelation( + executeRequest(globalErrorReceiver) { + roomAPI.sendRelation( roomId = roomId, parentId = relatedEventId, relationType = relationType, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/ReportContentTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/ReportContentTask.kt index 9c6e9907a4..29d507dfc5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/ReportContentTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/ReportContentTask.kt @@ -38,7 +38,7 @@ internal class DefaultReportContentTask @Inject constructor( override suspend fun execute(params: ReportContentTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = roomAPI.reportContent(params.roomId, params.eventId, ReportContentBody(params.score, params.reason)) + roomAPI.reportContent(params.roomId, params.eventId, ReportContentBody(params.score, params.reason)) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt index c901c7e18e..306f865408 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt @@ -55,8 +55,8 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) override suspend fun doSafeWork(params: Params): Result { val eventId = params.eventId return runCatching { - executeRequest(globalErrorReceiver) { - apiCall = roomAPI.redactEvent( + executeRequest(globalErrorReceiver) { + roomAPI.redactEvent( params.txID, params.roomId, eventId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt index c1fc2fd9fe..d55dce57af 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt @@ -91,7 +91,7 @@ internal class SendEventWorker(context: Context, if (/*currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING ||**/ !exception.shouldBeRetried()) { Timber.e("## SendEvent: [${System.currentTimeMillis()}] Send event Failed cannot retry ${params.eventId} > ${exception.localizedMessage}") localEchoRepository.updateSendState(event.eventId, event.roomId, SendState.UNDELIVERED) - return Result.success() + Result.success() } else { Timber.e("## SendEvent: [${System.currentTimeMillis()}] Send event Failed schedule retry ${params.eventId} > ${exception.localizedMessage}") Result.retry() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt index 2972332989..a5c09f5ff6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt @@ -24,6 +24,7 @@ import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError +import org.matrix.android.sdk.api.failure.getRetryDelay import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.util.Cancelable @@ -148,8 +149,7 @@ internal class EventSenderProcessorCoroutine @Inject constructor( task.markAsFailedOrRetry(exception, 0) } (exception is Failure.ServerError && exception.error.code == MatrixError.M_LIMIT_EXCEEDED) -> { - val delay = exception.error.retryAfterMillis?.plus(100) ?: 3_000 - task.markAsFailedOrRetry(exception, delay) + task.markAsFailedOrRetry(exception, exception.getRetryDelay(3_000)) } exception is CancellationException -> { Timber.v("## $task has been cancelled, try next task") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt index 63691d9207..998e116a0e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt @@ -39,7 +39,7 @@ internal class DefaultSendStateTask @Inject constructor( override suspend fun execute(params: SendStateTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = if (params.stateKey == null) { + if (params.stateKey == null) { roomAPI.sendStateEvent( roomId = params.roomId, stateEventType = params.eventType, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index 107055b8c3..dd3fbe04b2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -18,10 +18,18 @@ package org.matrix.android.sdk.internal.session.room.summary import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations +import androidx.paging.LivePagedListBuilder +import androidx.paging.PagedList import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import io.realm.RealmQuery +import io.realm.Sort +import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams +import org.matrix.android.sdk.api.session.room.UpdatableFilterLivePageResult import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.VersioningState +import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper @@ -32,8 +40,6 @@ import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.query.process import org.matrix.android.sdk.internal.util.fetchCopyMap -import io.realm.Realm -import io.realm.RealmQuery import javax.inject.Inject internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy, @@ -98,6 +104,62 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat .sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX) } + fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config): LiveData> { + val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> + roomSummariesQuery(realm, queryParams) + .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) + } + val dataSourceFactory = realmDataSourceFactory.map { + roomSummaryMapper.map(it) + } + return monarchy.findAllPagedWithChanges( + realmDataSourceFactory, + LivePagedListBuilder(dataSourceFactory, pagedListConfig) + ) + } + + fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config): UpdatableFilterLivePageResult { + val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> + roomSummariesQuery(realm, queryParams) + .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) + } + val dataSourceFactory = realmDataSourceFactory.map { + roomSummaryMapper.map(it) + } + + val mapped = monarchy.findAllPagedWithChanges( + realmDataSourceFactory, + LivePagedListBuilder(dataSourceFactory, pagedListConfig) + ) + + return object : UpdatableFilterLivePageResult { + override val livePagedList: LiveData> = mapped + + override fun updateQuery(queryParams: RoomSummaryQueryParams) { + realmDataSourceFactory.updateQuery { + roomSummariesQuery(it, queryParams) + .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) + } + } + } + } + + fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount { + var notificationCount: RoomAggregateNotificationCount? = null + monarchy.doWithRealm { realm -> + val roomSummariesQuery = roomSummariesQuery(realm, queryParams) + val notifCount = roomSummariesQuery.sum(RoomSummaryEntityFields.NOTIFICATION_COUNT).toInt() + val highlightCount = roomSummariesQuery.sum(RoomSummaryEntityFields.HIGHLIGHT_COUNT).toInt() + notificationCount = RoomAggregateNotificationCount( + notifCount, + highlightCount + ) + } + return notificationCount!! + } + private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery { val query = RoomSummaryEntity.where(realm) query.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId) @@ -105,6 +167,28 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias) query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) query.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name) + + queryParams.roomCategoryFilter?.let { + when (it) { + RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true) + RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false) + RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0) + RoomCategoryFilter.ALL -> { + // nop + } + } + } + queryParams.roomTagQueryFilter?.let { + it.isFavorite?.let { fav -> + query.equalTo(RoomSummaryEntityFields.IS_FAVOURITE, fav) + } + it.isLowPriority?.let { lp -> + query.equalTo(RoomSummaryEntityFields.IS_LOW_PRIORITY, lp) + } + it.isServerNotice?.let { sn -> + query.equalTo(RoomSummaryEntityFields.IS_SERVER_NOTICE, sn) + } + } return query } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index cd1bb69612..7913bf71a2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -98,11 +98,16 @@ internal class RoomSummaryUpdater @Inject constructor( val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) + val lastActivityFromEvent = latestPreviewableEvent?.root?.originServerTs + if (lastActivityFromEvent != null) { + roomSummaryEntity.lastActivityTime = lastActivityFromEvent + } + roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 // avoid this call if we are sure there are unread events || !isEventRead(realm.configuration, userId, roomId, latestPreviewableEvent?.eventId) - roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId).toString() + roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId) roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId) roomSummaryEntity.name = ContentMapper.map(lastNameEvent?.content).toModel()?.name roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel()?.topic @@ -112,9 +117,7 @@ internal class RoomSummaryUpdater @Inject constructor( val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel()?.aliases .orEmpty() - roomSummaryEntity.aliases.clear() - roomSummaryEntity.aliases.addAll(roomAliases) - roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|") + roomSummaryEntity.updateAliases(roomAliases) roomSummaryEntity.isEncrypted = encryptionEvent != null roomSummaryEntity.encryptionEventTs = encryptionEvent?.originServerTs diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/AddTagToRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/AddTagToRoomTask.kt index c3b5c3f78f..3e82d674ce 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/AddTagToRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/AddTagToRoomTask.kt @@ -39,8 +39,8 @@ internal class DefaultAddTagToRoomTask @Inject constructor( ) : AddTagToRoomTask { override suspend fun execute(params: AddTagToRoomTask.Params) { - executeRequest(globalErrorReceiver) { - apiCall = roomAPI.putTag( + executeRequest(globalErrorReceiver) { + roomAPI.putTag( userId = userId, roomId = params.roomId, tag = params.tag, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DeleteTagFromRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DeleteTagFromRoomTask.kt index d578d21fde..ae2a050659 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DeleteTagFromRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DeleteTagFromRoomTask.kt @@ -38,8 +38,8 @@ internal class DefaultDeleteTagFromRoomTask @Inject constructor( ) : DeleteTagFromRoomTask { override suspend fun execute(params: DeleteTagFromRoomTask.Params) { - executeRequest(globalErrorReceiver) { - apiCall = roomAPI.deleteTag( + executeRequest(globalErrorReceiver) { + roomAPI.deleteTag( userId = userId, roomId = params.roomId, tag = params.tag diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 1ed142ce23..e230599f8f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -386,14 +386,14 @@ internal class DefaultTimeline( private fun getState(direction: Timeline.Direction): TimelineState { return when (direction) { - Timeline.Direction.FORWARDS -> forwardsState.get() + Timeline.Direction.FORWARDS -> forwardsState.get() Timeline.Direction.BACKWARDS -> backwardsState.get() } } private fun updateState(direction: Timeline.Direction, update: (TimelineState) -> TimelineState) { val stateReference = when (direction) { - Timeline.Direction.FORWARDS -> forwardsState + Timeline.Direction.FORWARDS -> forwardsState Timeline.Direction.BACKWARDS -> backwardsState } val currentValue = stateReference.get() @@ -604,12 +604,14 @@ internal class DefaultTimeline( return offsetResults.size } - private fun buildTimelineEvent(eventEntity: TimelineEventEntity) = timelineEventMapper.map( - timelineEventEntity = eventEntity, - buildReadReceipts = settings.buildReadReceipts - ).let { - // eventually enhance with ui echo? - (uiEchoManager.decorateEventWithReactionUiEcho(it) ?: it) + private fun buildTimelineEvent(eventEntity: TimelineEventEntity): TimelineEvent { + return timelineEventMapper.map( + timelineEventEntity = eventEntity, + buildReadReceipts = settings.buildReadReceipts + ).let { timelineEvent -> + // eventually enhance with ui echo? + uiEchoManager.decorateEventWithReactionUiEcho(timelineEvent) ?: timelineEvent + } } /** @@ -702,10 +704,10 @@ internal class DefaultTimeline( return object : MatrixCallback { override fun onSuccess(data: TokenChunkEventPersistor.Result) { when (data) { - TokenChunkEventPersistor.Result.SUCCESS -> { + TokenChunkEventPersistor.Result.SUCCESS -> { Timber.v("Success fetching $limit items $direction from pagination request") } - TokenChunkEventPersistor.Result.REACHED_END -> { + TokenChunkEventPersistor.Result.REACHED_END -> { postSnapshot() } TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt index 76c4b3812c..96646b42ed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt @@ -48,8 +48,8 @@ internal class DefaultFetchTokenAndPaginateTask @Inject constructor( override suspend fun execute(params: FetchTokenAndPaginateTask.Params): TokenChunkEventPersistor.Result { val filter = filterRepository.getRoomFilter() - val response = executeRequest(globalErrorReceiver) { - apiCall = roomAPI.getContextOfEvent(params.roomId, params.lastKnownEventId, 0, filter) + val response = executeRequest(globalErrorReceiver) { + roomAPI.getContextOfEvent(params.roomId, params.lastKnownEventId, 0, filter) } val fromToken = if (params.direction == PaginationDirection.FORWARDS) { response.end diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt index d02a7bafe9..015e55f070 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt @@ -40,9 +40,9 @@ internal class DefaultGetContextOfEventTask @Inject constructor( override suspend fun execute(params: GetContextOfEventTask.Params): TokenChunkEventPersistor.Result { val filter = filterRepository.getRoomFilter() - val response = executeRequest(globalErrorReceiver) { + val response = executeRequest(globalErrorReceiver) { // We are limiting the response to the event with eventId to be sure we don't have any issue with potential merging process. - apiCall = roomAPI.getContextOfEvent(params.roomId, params.eventId, 0, filter) + roomAPI.getContextOfEvent(params.roomId, params.eventId, 0, filter) } return tokenChunkEventPersistor.insertInDb(response, params.roomId, PaginationDirection.FORWARDS) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt index b8585b1e74..cbbc54e90d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt @@ -16,28 +16,49 @@ package org.matrix.android.sdk.internal.session.room.timeline +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.crypto.EventDecryptor +import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject -// TODO Add parent task - -internal class GetEventTask @Inject constructor( - private val roomAPI: RoomAPI, - private val globalErrorReceiver: GlobalErrorReceiver -) : Task { - - internal data class Params( +internal interface GetEventTask : Task { + data class Params( val roomId: String, val eventId: String ) +} - override suspend fun execute(params: Params): Event { - return executeRequest(globalErrorReceiver) { - apiCall = roomAPI.getEvent(params.roomId, params.eventId) +internal class DefaultGetEventTask @Inject constructor( + private val roomAPI: RoomAPI, + private val globalErrorReceiver: GlobalErrorReceiver, + private val eventDecryptor: EventDecryptor +) : GetEventTask { + + override suspend fun execute(params: GetEventTask.Params): Event { + val event = executeRequest(globalErrorReceiver) { + roomAPI.getEvent(params.roomId, params.eventId) } + + // Try to decrypt the Event + if (event.isEncrypted()) { + tryOrNull(message = "Unable to decrypt the event") { + eventDecryptor.decryptEvent(event, "") + } + ?.let { result -> + event.mxDecryptionResult = OlmDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + } + } + + return event } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt index 1f99893e17..8aeccb66c8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt @@ -42,9 +42,11 @@ internal class DefaultPaginationTask @Inject constructor( override suspend fun execute(params: PaginationTask.Params): TokenChunkEventPersistor.Result { val filter = filterRepository.getRoomFilter() - val chunk = executeRequest(globalErrorReceiver) { - isRetryable = true - apiCall = roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter) + val chunk = executeRequest( + globalErrorReceiver, + canRetry = true + ) { + roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter) } return tokenChunkEventPersistor.insertInDb(chunk, params.roomId, params.direction) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt index 3b56d04872..0b0df74311 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt @@ -44,8 +44,8 @@ internal class DefaultSendTypingTask @Inject constructor( override suspend fun execute(params: SendTypingTask.Params) { delay(params.delay ?: -1) - executeRequest(globalErrorReceiver) { - apiCall = roomAPI.sendTypingState( + executeRequest(globalErrorReceiver) { + roomAPI.sendTypingState( params.roomId, userId, TypingBody(params.isTyping, params.typingTimeoutMillis?.takeIf { params.isTyping }) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt index b3e4a5aa05..028c3e9193 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt @@ -37,7 +37,6 @@ import org.matrix.android.sdk.internal.session.filter.FilterFactory import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection -import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse import org.matrix.android.sdk.internal.session.sync.SyncTokenStore import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -86,8 +85,8 @@ internal class DefaultGetUploadsTask @Inject constructor( val since = params.since ?: tokenStore.getLastToken() ?: throw IllegalStateException("No token available") val filter = FilterFactory.createUploadsFilter(params.numberOfEvents).toJSONString() - val chunk = executeRequest(globalErrorReceiver) { - apiCall = roomAPI.getRoomMessagesFrom(params.roomId, since, PaginationDirection.BACKWARDS.value, params.numberOfEvents, filter) + val chunk = executeRequest(globalErrorReceiver) { + roomAPI.getRoomMessagesFrom(params.roomId, since, PaginationDirection.BACKWARDS.value, params.numberOfEvents, filter) } result = GetUploadsResult( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchAPI.kt index 4a74b0a023..b5099e7238 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchAPI.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.search import org.matrix.android.sdk.internal.network.NetworkConstants import org.matrix.android.sdk.internal.session.search.request.SearchRequestBody import org.matrix.android.sdk.internal.session.search.response.SearchResponse -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.POST import retrofit2.http.Query @@ -31,6 +30,6 @@ internal interface SearchAPI { * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-search */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "search") - fun search(@Query("next_batch") nextBatch: String?, - @Body body: SearchRequestBody): Call + suspend fun search(@Query("next_batch") nextBatch: String?, + @Body body: SearchRequestBody): SearchResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt index 402602e4d5..8de762ee1b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt @@ -51,25 +51,25 @@ internal class DefaultSearchTask @Inject constructor( ) : SearchTask { override suspend fun execute(params: SearchTask.Params): SearchResult { - return executeRequest(globalErrorReceiver) { - val searchRequestBody = SearchRequestBody( - searchCategories = SearchRequestCategories( - roomEvents = SearchRequestRoomEvents( - searchTerm = params.searchTerm, - orderBy = if (params.orderByRecent) SearchRequestOrder.RECENT else SearchRequestOrder.RANK, - filter = SearchRequestFilter( - limit = params.limit, - rooms = listOf(params.roomId) - ), - eventContext = SearchRequestEventContext( - beforeLimit = params.beforeLimit, - afterLimit = params.afterLimit, - includeProfile = params.includeProfile - ) - ) - ) - ) - apiCall = searchAPI.search(params.nextBatch, searchRequestBody) + val searchRequestBody = SearchRequestBody( + searchCategories = SearchRequestCategories( + roomEvents = SearchRequestRoomEvents( + searchTerm = params.searchTerm, + orderBy = if (params.orderByRecent) SearchRequestOrder.RECENT else SearchRequestOrder.RANK, + filter = SearchRequestFilter( + limit = params.limit, + rooms = listOf(params.roomId) + ), + eventContext = SearchRequestEventContext( + beforeLimit = params.beforeLimit, + afterLimit = params.afterLimit, + includeProfile = params.includeProfile + ) + ) + ) + ) + return executeRequest(globalErrorReceiver) { + searchAPI.search(params.nextBatch, searchRequestBody) }.toDomain() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt index 2c3cd5d270..563e85aefc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.session.signout -import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.internal.auth.SessionParamsStore import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams @@ -39,8 +38,8 @@ internal class DefaultSignInAgainTask @Inject constructor( ) : SignInAgainTask { override suspend fun execute(params: SignInAgainTask.Params) { - val newCredentials = executeRequest(globalErrorReceiver) { - apiCall = signOutAPI.loginAgain( + val newCredentials = executeRequest(globalErrorReceiver) { + signOutAPI.loginAgain( PasswordLoginParams.userIdentifier( // Reuse the same userId sessionParams.userId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutAPI.kt index 4c92938b77..a56362e587 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutAPI.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.signout import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.Headers import retrofit2.http.POST @@ -35,11 +34,11 @@ internal interface SignOutAPI { */ @Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login") - fun loginAgain(@Body loginParams: PasswordLoginParams): Call + suspend fun loginAgain(@Body loginParams: PasswordLoginParams): Credentials /** * Invalidate the access token, so that it can no longer be used for authorization. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "logout") - fun signOut(): Call + suspend fun signOut() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt index 0cb8704782..9c25eccb3a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt @@ -45,8 +45,8 @@ internal class DefaultSignOutTask @Inject constructor( if (params.signOutFromHomeserver) { Timber.d("SignOut: send request...") try { - executeRequest(globalErrorReceiver) { - apiCall = signOutAPI.signOut() + executeRequest(globalErrorReceiver) { + signOutAPI.signOut() } } catch (throwable: Throwable) { // Maybe due to https://github.com/matrix-org/synapse/issues/5756 diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt index e938d54903..2bb606e921 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt @@ -49,6 +49,7 @@ import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.extensions.clearWith +import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent import org.matrix.android.sdk.internal.session.initsync.ProgressReporter import org.matrix.android.sdk.internal.session.initsync.mapWithProgress import org.matrix.android.sdk.internal.session.initsync.reportSubtask @@ -407,6 +408,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle private fun decryptIfNeeded(event: Event, roomId: String) { try { + // Event from sync does not have roomId, so add it to the event first val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "") event.mxDecryptionResult = OlmDecryptionResult( payload = result.clearEvent, @@ -464,18 +466,4 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } } } - - private fun Event.getFixedRoomMemberContent(): RoomMemberContent? { - val content = content.toModel() - // if user is leaving, we should grab his last name and avatar from prevContent - return if (content?.membership?.isLeft() == true) { - val prevContent = resolvedPrevContent().toModel() - content.copy( - displayName = prevContent?.displayName, - avatarUrl = prevContent?.avatarUrl - ) - } else { - content - } - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt index f9ae41bc94..add5d841d1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt @@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.session.sync import org.matrix.android.sdk.api.session.room.model.tag.RoomTagContent import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomTagEntity -import org.matrix.android.sdk.internal.database.query.where import io.realm.Realm +import org.matrix.android.sdk.internal.database.query.getOrCreate import javax.inject.Inject internal class RoomTagHandler @Inject constructor() { @@ -31,12 +31,8 @@ internal class RoomTagHandler @Inject constructor() { } val tags = content.tags.entries.map { (tagName, params) -> RoomTagEntity(tagName, params["order"] as? Double) + Pair(tagName, params["order"] as? Double) } - val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() - ?: RoomSummaryEntity(roomId) - - roomSummaryEntity.tags.clear() - roomSummaryEntity.tags.addAll(tags) - realm.insertOrUpdate(roomSummaryEntity) + RoomSummaryEntity.getOrCreate(realm, roomId).updateTags(tags) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt index 8e3523bc57..2616803463 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt @@ -31,11 +31,11 @@ internal interface SyncAPI { * Set all the timeouts to 1 minute by default */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sync") - fun sync(@QueryMap params: Map, - @Header(TimeOutInterceptor.CONNECT_TIMEOUT) connectTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT, - @Header(TimeOutInterceptor.READ_TIMEOUT) readTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT, - @Header(TimeOutInterceptor.WRITE_TIMEOUT) writeTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT - ): Call + suspend fun sync(@QueryMap params: Map, + @Header(TimeOutInterceptor.CONNECT_TIMEOUT) connectTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT, + @Header(TimeOutInterceptor.READ_TIMEOUT) readTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT, + @Header(TimeOutInterceptor.WRITE_TIMEOUT) writeTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT + ): SyncResponse /** * Set all the timeouts to 1 minute by default diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt index 8d8d69be1e..83a2ffc446 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt @@ -29,7 +29,6 @@ import org.matrix.android.sdk.internal.session.homeserver.GetHomeServerCapabilit import org.matrix.android.sdk.internal.session.initsync.DefaultInitialSyncProgressService import org.matrix.android.sdk.internal.session.initsync.reportSubtask import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral -import org.matrix.android.sdk.internal.session.sync.model.SyncResponse import org.matrix.android.sdk.internal.session.sync.parsing.InitialSyncResponseParser import org.matrix.android.sdk.internal.session.user.UserStore import org.matrix.android.sdk.internal.task.Task @@ -115,8 +114,8 @@ internal class DefaultSyncTask @Inject constructor( workingDir.deleteRecursively() } else { val syncResponse = logDuration("INIT_SYNC Request") { - executeRequest(globalErrorReceiver) { - apiCall = syncAPI.sync( + executeRequest(globalErrorReceiver) { + syncAPI.sync( params = requestParams, readTimeOut = readTimeOut ) @@ -130,8 +129,8 @@ internal class DefaultSyncTask @Inject constructor( } initialSyncProgressService.endAll() } else { - val syncResponse = executeRequest(globalErrorReceiver) { - apiCall = syncAPI.sync( + val syncResponse = executeRequest(globalErrorReceiver) { + syncAPI.sync( params = requestParams, readTimeOut = readTimeOut ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt index 907c1187fe..b8d987d500 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt @@ -46,6 +46,7 @@ import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver +import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.session.sync.model.InvitedRoomSync import org.matrix.android.sdk.internal.session.sync.model.accountdata.BreadcrumbsContent @@ -62,7 +63,8 @@ internal class UserAccountDataSyncHandler @Inject constructor( @UserId private val userId: String, private val directChatsHelper: DirectChatsHelper, private val updateUserAccountDataTask: UpdateUserAccountDataTask, - private val roomAvatarResolver: RoomAvatarResolver + private val roomAvatarResolver: RoomAvatarResolver, + private val roomDisplayNameResolver: RoomDisplayNameResolver ) { fun handle(realm: Realm, accountData: UserAccountDataSync?) { @@ -161,8 +163,9 @@ internal class UserAccountDataSyncHandler @Inject constructor( if (roomSummaryEntity != null) { roomSummaryEntity.isDirect = true roomSummaryEntity.directUserId = userId - // Also update the avatar, there is a specific treatment for DMs + // Also update the avatar and displayname, there is a specific treatment for DMs roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId) + roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId) } } } @@ -172,8 +175,9 @@ internal class UserAccountDataSyncHandler @Inject constructor( .forEach { it.isDirect = false it.directUserId = null - // Also update the avatar, there was a specific treatment for DMs + // Also update the avatar and displayname, there was a specific treatment for DMs it.avatarUrl = roomAvatarResolver.resolve(realm, it.roomId) + it.displayName = roomDisplayNameResolver.resolve(realm, it.roomId) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt index 41914cc799..bac725fad2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt @@ -17,7 +17,8 @@ package org.matrix.android.sdk.internal.session.terms import dagger.Lazy -import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.terms.GetTermsResponse import org.matrix.android.sdk.api.session.terms.TermsService @@ -29,12 +30,9 @@ import org.matrix.android.sdk.internal.session.identity.IdentityAuthAPI import org.matrix.android.sdk.internal.session.identity.IdentityRegisterTask import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask import org.matrix.android.sdk.internal.session.sync.model.accountdata.AcceptedTermsContent -import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.ensureTrailingSlash -import okhttp3.OkHttpClient import javax.inject.Inject internal class DefaultTermsService @Inject constructor( @@ -45,43 +43,39 @@ internal class DefaultTermsService @Inject constructor( private val retrofitFactory: RetrofitFactory, private val getOpenIdTokenTask: GetOpenIdTokenTask, private val identityRegisterTask: IdentityRegisterTask, - private val updateUserAccountDataTask: UpdateUserAccountDataTask, - private val coroutineDispatchers: MatrixCoroutineDispatchers + private val updateUserAccountDataTask: UpdateUserAccountDataTask ) : TermsService { + override suspend fun getTerms(serviceType: TermsService.ServiceType, - baseUrl: String): GetTermsResponse { - return withContext(coroutineDispatchers.main) { - val url = buildUrl(baseUrl, serviceType) - val termsResponse = executeRequest(null) { - apiCall = termsAPI.getTerms("${url}terms") - } - GetTermsResponse(termsResponse, getAlreadyAcceptedTermUrlsFromAccountData()) + baseUrl: String): GetTermsResponse { + val url = buildUrl(baseUrl, serviceType) + val termsResponse = executeRequest(null) { + termsAPI.getTerms("${url}terms") } + return GetTermsResponse(termsResponse, getAlreadyAcceptedTermUrlsFromAccountData()) } override suspend fun agreeToTerms(serviceType: TermsService.ServiceType, baseUrl: String, agreedUrls: List, token: String?) { - withContext(coroutineDispatchers.main) { - val url = buildUrl(baseUrl, serviceType) - val tokenToUse = token?.takeIf { it.isNotEmpty() } ?: getToken(baseUrl) + val url = buildUrl(baseUrl, serviceType) + val tokenToUse = token?.takeIf { it.isNotEmpty() } ?: getToken(baseUrl) - executeRequest(null) { - apiCall = termsAPI.agreeToTerms("${url}terms", AcceptTermsBody(agreedUrls), "Bearer $tokenToUse") - } - - // client SHOULD update this account data section adding any the URLs - // of any additional documents that the user agreed to this list. - // Get current m.accepted_terms append new ones and update account data - val listOfAcceptedTerms = getAlreadyAcceptedTermUrlsFromAccountData() - - val newList = listOfAcceptedTerms.toMutableSet().apply { addAll(agreedUrls) }.toList() - - updateUserAccountDataTask.execute(UpdateUserAccountDataTask.AcceptedTermsParams( - acceptedTermsContent = AcceptedTermsContent(newList) - )) + executeRequest(null) { + termsAPI.agreeToTerms("${url}terms", AcceptTermsBody(agreedUrls), "Bearer $tokenToUse") } + + // client SHOULD update this account data section adding any the URLs + // of any additional documents that the user agreed to this list. + // Get current m.accepted_terms append new ones and update account data + val listOfAcceptedTerms = getAlreadyAcceptedTermUrlsFromAccountData() + + val newList = listOfAcceptedTerms.toMutableSet().apply { addAll(agreedUrls) }.toList() + + updateUserAccountDataTask.execute(UpdateUserAccountDataTask.AcceptedTermsParams( + acceptedTermsContent = AcceptedTermsContent(newList) + )) } private suspend fun getToken(url: String): String { @@ -97,7 +91,7 @@ internal class DefaultTermsService @Inject constructor( private fun buildUrl(baseUrl: String, serviceType: TermsService.ServiceType): String { val servicePath = when (serviceType) { TermsService.ServiceType.IntegrationManager -> NetworkConstants.URI_INTEGRATION_MANAGER_PATH - TermsService.ServiceType.IdentityService -> NetworkConstants.URI_IDENTITY_PATH_V2 + TermsService.ServiceType.IdentityService -> NetworkConstants.URI_IDENTITY_PATH_V2 } return "${baseUrl.ensureTrailingSlash()}$servicePath" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt index 4c97f462eb..91d27030de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.terms import org.matrix.android.sdk.internal.network.HttpHeaders -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Header @@ -29,13 +28,13 @@ internal interface TermsAPI { * This request does not require authentication */ @GET - fun getTerms(@Url url: String): Call + suspend fun getTerms(@Url url: String): TermsResponse /** * This request requires authentication */ @POST - fun agreeToTerms(@Url url: String, - @Body params: AcceptTermsBody, - @Header(HttpHeaders.Authorization) token: String): Call + suspend fun agreeToTerms(@Url url: String, + @Body params: AcceptTermsBody, + @Header(HttpHeaders.Authorization) token: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyProtocolsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyProtocolsTask.kt index fd1ed741e9..026e17b513 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyProtocolsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyProtocolsTask.kt @@ -31,7 +31,7 @@ internal class DefaultGetThirdPartyProtocolsTask @Inject constructor( override suspend fun execute(params: Unit): Map { return executeRequest(globalErrorReceiver) { - apiCall = thirdPartyAPI.thirdPartyProtocols() + thirdPartyAPI.thirdPartyProtocols() } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyUserTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyUserTask.kt index 01a8b57678..f541dcb814 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyUserTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyUserTask.kt @@ -37,7 +37,7 @@ internal class DefaultGetThirdPartyUserTask @Inject constructor( override suspend fun execute(params: GetThirdPartyUserTask.Params): List { return executeRequest(globalErrorReceiver) { - apiCall = thirdPartyAPI.getThirdPartyUser(params.protocol, params.fields) + thirdPartyAPI.getThirdPartyUser(params.protocol, params.fields) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt index 0c60a27341..2e03bc7a86 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.thirdparty import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol import org.matrix.android.sdk.api.session.thirdparty.model.ThirdPartyUser import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Path import retrofit2.http.QueryMap @@ -32,7 +31,7 @@ internal interface ThirdPartyAPI { * Ref: https://matrix.org/docs/spec/client_server/r0.6.1.html#get-matrix-client-r0-thirdparty-protocols */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/protocols") - fun thirdPartyProtocols(): Call> + suspend fun thirdPartyProtocols(): Map /** * Retrieve a Matrix User ID linked to a user on the third party service, given a set of user parameters. @@ -40,5 +39,6 @@ internal interface ThirdPartyAPI { * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-thirdparty-user-protocol */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/protocols/user/{protocol}") - fun getThirdPartyUser(@Path("protocol") protocol: String, @QueryMap params: Map?): Call> + suspend fun getThirdPartyUser(@Path("protocol") protocol: String, + @QueryMap params: Map?): List } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/SearchUserAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/SearchUserAPI.kt index c5c546bbed..e03d406639 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/SearchUserAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/SearchUserAPI.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.user import org.matrix.android.sdk.internal.network.NetworkConstants import org.matrix.android.sdk.internal.session.user.model.SearchUsersParams import org.matrix.android.sdk.internal.session.user.model.SearchUsersResponse -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.POST @@ -31,5 +30,5 @@ internal interface SearchUserAPI { * @param searchUsersParams the search params. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user_directory/search") - fun searchUsers(@Body searchUsersParams: SearchUsersParams): Call + suspend fun searchUsers(@Body searchUsersParams: SearchUsersParams): SearchUsersResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt index 3de484fab3..cc5625b255 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.user.accountdata import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.PUT import retrofit2.http.Path @@ -32,7 +31,7 @@ interface AccountDataAPI { * @param params the put params */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/account_data/{type}") - fun setAccountData(@Path("userId") userId: String, - @Path("type") type: String, - @Body params: Any): Call + suspend fun setAccountData(@Path("userId") userId: String, + @Path("type") type: String, + @Body params: Any) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt index 26e8d3380a..445b78104c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt @@ -63,8 +63,8 @@ internal class DefaultUpdateIgnoredUserIdsTask @Inject constructor( val list = ignoredUserIds.toList() val body = IgnoredUsersContent.createWithUserIds(list) - executeRequest(globalErrorReceiver) { - apiCall = accountDataApi.setAccountData(userId, UserAccountDataTypes.TYPE_IGNORED_USER_LIST, body) + executeRequest(globalErrorReceiver) { + accountDataApi.setAccountData(userId, UserAccountDataTypes.TYPE_IGNORED_USER_LIST, body) } // Update the DB right now (do not wait for the sync to come back with updated data, for a faster UI update) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt index dba28253a7..1a588d2245 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt @@ -105,7 +105,7 @@ internal class DefaultUpdateUserAccountDataTask @Inject constructor( override suspend fun execute(params: UpdateUserAccountDataTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = accountDataApi.setAccountData(userId, params.type, params.getData()) + accountDataApi.setAccountData(userId, params.type, params.getData()) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/model/SearchUserTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/model/SearchUserTask.kt index 380fa6e209..5a8779f40f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/model/SearchUserTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/model/SearchUserTask.kt @@ -38,8 +38,8 @@ internal class DefaultSearchUserTask @Inject constructor( ) : SearchUserTask { override suspend fun execute(params: SearchUserTask.Params): List { - val response = executeRequest(globalErrorReceiver) { - apiCall = searchUserAPI.searchUsers(SearchUsersParams(params.search, params.limit)) + val response = executeRequest(globalErrorReceiver) { + searchUserAPI.searchUsers(SearchUsersParams(params.search, params.limit)) } return response.users.map { User(it.userId, it.displayName, it.avatarUrl) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt index ae807ce30f..18a043be45 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt @@ -46,8 +46,8 @@ internal class DefaultCreateWidgetTask @Inject constructor(@SessionDatabase priv private val globalErrorReceiver: GlobalErrorReceiver) : CreateWidgetTask { override suspend fun execute(params: CreateWidgetTask.Params) { - executeRequest(globalErrorReceiver) { - apiCall = roomAPI.sendStateEvent( + executeRequest(globalErrorReceiver) { + roomAPI.sendStateEvent( roomId = params.roomId, stateEventType = EventType.STATE_ROOM_WIDGET_LEGACY, stateKey = params.widgetId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt index 1fece8b580..6652628026 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.session.widgets import org.matrix.android.sdk.internal.session.openid.RequestOpenIdTokenResponse -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST @@ -30,10 +29,10 @@ internal interface WidgetsAPI { * @param body the body content (Ref: https://github.com/matrix-org/matrix-doc/pull/1961) */ @POST("register") - fun register(@Body body: RequestOpenIdTokenResponse, - @Query("v") version: String?): Call + suspend fun register(@Body body: RequestOpenIdTokenResponse, + @Query("v") version: String?): RegisterWidgetResponse @GET("account") - fun validateToken(@Query("scalar_token") scalarToken: String?, - @Query("v") version: String?): Call + suspend fun validateToken(@Query("scalar_token") scalarToken: String?, + @Query("v") version: String?) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/token/GetScalarTokenTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/token/GetScalarTokenTask.kt index 6db79da35f..78a40d1977 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/token/GetScalarTokenTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/token/GetScalarTokenTask.kt @@ -20,7 +20,6 @@ import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask -import org.matrix.android.sdk.internal.session.widgets.RegisterWidgetResponse import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure import org.matrix.android.sdk.internal.session.widgets.WidgetsAPI import org.matrix.android.sdk.internal.session.widgets.WidgetsAPIProvider @@ -59,8 +58,8 @@ internal class DefaultGetScalarTokenTask @Inject constructor(private val widgets private suspend fun getNewScalarToken(widgetsAPI: WidgetsAPI, serverUrl: String): String { val openId = getOpenIdTokenTask.execute(Unit) - val registerWidgetResponse = executeRequest(null) { - apiCall = widgetsAPI.register(openId, WIDGET_API_VERSION) + val registerWidgetResponse = executeRequest(null) { + widgetsAPI.register(openId, WIDGET_API_VERSION) } if (registerWidgetResponse.scalarToken == null) { // Should not happen @@ -72,8 +71,8 @@ internal class DefaultGetScalarTokenTask @Inject constructor(private val widgets private suspend fun validateToken(widgetsAPI: WidgetsAPI, serverUrl: String, scalarToken: String): String { return try { - executeRequest(null) { - apiCall = widgetsAPI.validateToken(scalarToken, WIDGET_API_VERSION) + executeRequest(null) { + widgetsAPI.validateToken(scalarToken, WIDGET_API_VERSION) } scalarToken } catch (failure: Throwable) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt index 3f0e27f410..7a9beac8c0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt @@ -89,8 +89,8 @@ internal class DefaultGetWellknownTask @Inject constructor( .create(WellKnownAPI::class.java) return try { - val wellKnown = executeRequest(null) { - apiCall = wellKnownAPI.getWellKnown(domain) + val wellKnown = executeRequest(null) { + wellKnownAPI.getWellKnown(domain) } // Success @@ -140,8 +140,8 @@ internal class DefaultGetWellknownTask @Inject constructor( .create(CapabilitiesAPI::class.java) try { - executeRequest(null) { - apiCall = capabilitiesAPI.ping() + executeRequest(null) { + capabilitiesAPI.ping() } } catch (throwable: Throwable) { return WellknownResult.FailError @@ -178,8 +178,8 @@ internal class DefaultGetWellknownTask @Inject constructor( .create(IdentityAuthAPI::class.java) return try { - executeRequest(null) { - apiCall = identityPingApi.ping() + executeRequest(null) { + identityPingApi.ping() } true diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/WellKnownAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/WellKnownAPI.kt index 981d013f49..428f7f65c0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/WellKnownAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/WellKnownAPI.kt @@ -16,11 +16,10 @@ package org.matrix.android.sdk.internal.wellknown import org.matrix.android.sdk.api.auth.data.WellKnown -import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Path internal interface WellKnownAPI { @GET("https://{domain}/.well-known/matrix/client") - fun getWellKnown(@Path("domain") domain: String): Call + suspend fun getWellKnown(@Path("domain") domain: String): WellKnown } diff --git a/matrix-sdk-android/src/main/res/values-fi/strings_sas.xml b/matrix-sdk-android/src/main/res/values-fi/strings_sas.xml index b690fee4ed..12edb39070 100644 --- a/matrix-sdk-android/src/main/res/values-fi/strings_sas.xml +++ b/matrix-sdk-android/src/main/res/values-fi/strings_sas.xml @@ -35,7 +35,7 @@ Robotti Hattu Silmälasit - Mutteriavain + Kiintoavain Joulupukki Peukalo ylös Sateenvarjo diff --git a/matrix-sdk-android/src/main/res/values-ja/strings_sas.xml b/matrix-sdk-android/src/main/res/values-ja/strings_sas.xml index 618302eb4f..12f90e316d 100644 --- a/matrix-sdk-android/src/main/res/values-ja/strings_sas.xml +++ b/matrix-sdk-android/src/main/res/values-ja/strings_sas.xml @@ -3,18 +3,66 @@ + ライオン + ユニコーン + ブタ + ゾウ + うさぎ + パンダ + ニワトリ + ペンギン + + たこ + ちょうちょ + サボテン きのこ + 地球 + + + バナナ リンゴ + いちご + とうもろこし + ピザ ケーキ + ハート + スマイル ロボと + 帽子 めがね + スパナ + サンタ + いいね + + 砂時計 + 時計 + ギフト + 電球 + 鉛筆 + クリップ + はさみ + 錠前 + + 金槌 電話機 + 電車 自転車 + 飛行機 + ロケット + トロフィー + ボール + ギター + トランペット + ベル + いかり + ヘッドホン + フォルダ + ピン diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index 7b7c44b9fd..5a53ececec 100644 --- a/tools/check/forbidden_strings_in_code.txt +++ b/tools/check/forbidden_strings_in_code.txt @@ -161,7 +161,7 @@ Formatter\.formatShortFileSize===1 # android\.text\.TextUtils ### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt -enum class===93 +enum class===94 ### Do not import temporary legacy classes import org.matrix.android.sdk.internal.legacy.riot===3 diff --git a/tools/import_emojis.py b/tools/import_emojis.py index f5638175a9..30db3b0b13 100755 --- a/tools/import_emojis.py +++ b/tools/import_emojis.py @@ -22,7 +22,7 @@ emoji_picker_datasource_emojis = emoji_picker_datasource["emojis"] # Get official emoji list from unicode.org (Emoji List, v13.1 at time of writing) -print("Fetching emoji list from Unicode.org...",) +print("Fetching emoji list from Unicode.org...") req = requests.get("https://unicode.org/emoji/charts/emoji-list.html") soup = BeautifulSoup(req.content, 'html.parser') @@ -114,11 +114,13 @@ for emoji in emoji_picker_datasource_emojis: # If additional keywords exist, add them to emoji_picker_datasource_emojis # Avoid duplicates and keep order. Put official unicode.com keywords first and extend up with emojilib ones. - new_keywords = OrderedDict.fromkeys(emoji_picker_datasource_emojis[emoji]["j"] + emoji_additional_keywords).keys() + new_keywords = OrderedDict.fromkeys(emoji_picker_datasource_emojis[emoji]["j"] + emoji_additional_keywords) # Remove the ones derived from the unicode name - new_keywords = new_keywords - {emoji.replace("-", "_")} - {emoji.replace("-", " ")} - {emoji_name} + for keyword in [emoji.replace("-", "_")] + [emoji.replace("-", " ")] + [emoji_name]: + if keyword in new_keywords: + new_keywords.pop(keyword) # Write new keywords back - emoji_picker_datasource_emojis[emoji]["j"] = list(new_keywords) + emoji_picker_datasource_emojis[emoji]["j"] = list(new_keywords.keys()) # Filter out components from unicode 13.1 (as they are not suitable for single-emoji reactions) emoji_picker_datasource['categories'] = [x for x in emoji_picker_datasource['categories'] if x['id'] != 'component'] diff --git a/vector/build.gradle b/vector/build.gradle index bf164facee..760a5d0743 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -14,7 +14,7 @@ kapt { // Note: 2 digits max for each value ext.versionMajor = 1 ext.versionMinor = 1 -ext.versionPatch = 4 +ext.versionPatch = 5 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' @@ -296,8 +296,8 @@ dependencies { def markwon_version = '4.1.2' def big_image_viewer_version = '1.7.1' def glide_version = '4.12.0' - def moshi_version = '1.11.0' - def daggerVersion = '2.33' + def moshi_version = '1.12.0' + def daggerVersion = '2.34' def autofill_version = "1.1.0" def work_version = '2.5.0' def arch_version = '2.1.0' @@ -320,13 +320,13 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" - implementation "androidx.recyclerview:recyclerview:1.2.0-rc01" + implementation "androidx.recyclerview:recyclerview:1.2.0" implementation 'androidx.appcompat:appcompat:1.2.0' implementation "androidx.fragment:fragment-ktx:$fragment_version" implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation "androidx.sharetarget:sharetarget:1.1.0" implementation 'androidx.core:core-ktx:1.3.2' - implementation "androidx.media:media:1.2.1" + implementation "androidx.media:media:1.3.0" implementation "org.threeten:threetenbp:1.4.0:no-tzdb" implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.9.0" @@ -342,7 +342,7 @@ dependencies { implementation 'com.facebook.stetho:stetho:1.6.0' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.20' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.21' // rx implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0' @@ -423,7 +423,7 @@ dependencies { kapt "com.google.dagger:dagger-compiler:$daggerVersion" // gplay flavor only - gplayImplementation('com.google.firebase:firebase-messaging:21.0.1') { + gplayImplementation('com.google.firebase:firebase-messaging:21.1.0') { exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' @@ -442,7 +442,11 @@ dependencies { implementation('com.facebook.react:react-native-webrtc:1.87.3-jitsi-6624067@aar') // Jitsi - implementation('org.jitsi.react:jitsi-meet-sdk:3.1.0') + implementation('org.jitsi.react:jitsi-meet-sdk:3.1.0') { + exclude group: 'com.google.firebase' + exclude group: 'com.google.android.gms' + exclude group: 'com.android.installreferrer' + } // QR-code // Stick to 3.3.3 because of https://github.com/zxing/zxing/issues/1170 diff --git a/vector/src/androidTest/java/im/vector/app/features/roomdirectory/ExplicitTermFilterTest.kt b/vector/src/androidTest/java/im/vector/app/features/roomdirectory/ExplicitTermFilterTest.kt new file mode 100644 index 0000000000..b2beec5b66 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/features/roomdirectory/ExplicitTermFilterTest.kt @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomdirectory + +import im.vector.app.InstrumentedTest +import im.vector.app.core.utils.AssetReader +import org.amshove.kluent.shouldBe +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +class ExplicitTermFilterTest : InstrumentedTest { + + private val explicitTermFilter = ExplicitTermFilter(AssetReader(context())) + + @Test + fun isValidEmptyTrue() { + explicitTermFilter.isValid("") shouldBe true + } + + @Test + fun isValidTrue() { + explicitTermFilter.isValid("Hello") shouldBe true + } + + @Test + fun isValidFalse() { + explicitTermFilter.isValid("nsfw") shouldBe false + } + + @Test + fun isValidUpCaseFalse() { + explicitTermFilter.isValid("Nsfw") shouldBe false + } + + @Test + fun isValidMultilineTrue() { + explicitTermFilter.isValid("Hello\nWorld") shouldBe true + } + + @Test + fun isValidMultilineFalse() { + explicitTermFilter.isValid("Hello\nnsfw") shouldBe false + } + + @Test + fun isValidMultilineFalse2() { + explicitTermFilter.isValid("nsfw\nHello") shouldBe false + } + + @Test + fun isValidAnalFalse() { + explicitTermFilter.isValid("anal") shouldBe false + } + + @Test + fun isValidAnal2False() { + explicitTermFilter.isValid("There is some anal in this room") shouldBe false + } + + @Test + fun isValidAnalysisTrue() { + explicitTermFilter.isValid("analysis") shouldBe true + } + + @Test + fun isValidAnalysis2True() { + explicitTermFilter.isValid("There is some analysis in the room") shouldBe true + } + + @Test + fun isValidSpecialCharFalse() { + explicitTermFilter.isValid("18+") shouldBe false + } + + @Test + fun isValidSpecialChar2False() { + explicitTermFilter.isValid("This is a room with 18+ content") shouldBe false + } + + @Test + fun isValidOtherSpecialCharFalse() { + explicitTermFilter.isValid("strap-on") shouldBe false + } + + @Test + fun isValidOtherSpecialChar2False() { + explicitTermFilter.isValid("This is a room with strap-on content") shouldBe false + } + + @Test + fun isValid18True() { + explicitTermFilter.isValid("18") shouldBe true + } + + @Test + fun isValidLastFalse() { + explicitTermFilter.isValid("zoo") shouldBe false + } + + @Test + fun canSearchForFalse() { + explicitTermFilter.canSearchFor("zoo") shouldBe false + } + + @Test + fun canSearchForTrue() { + explicitTermFilter.canSearchFor("android") shouldBe true + } +} diff --git a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt index 6f8056de13..53e1645f09 100644 --- a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt +++ b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt @@ -78,6 +78,7 @@ class UiAllScreensSanityTest { // Last passing: // 2020-11-09 // 2020-12-16 After ViewBinding huge change + // 2021-04-08 Testing 429 change @Test fun allScreensTest() { // Create an account diff --git a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt index 4d2cbecfe4..4cefeadb62 100755 --- a/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt @@ -31,6 +31,7 @@ import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.vectorComponent +import im.vector.app.core.network.WifiDetector import im.vector.app.core.pushers.PushersManager import im.vector.app.features.badge.BadgeProxy import im.vector.app.features.notifications.NotifiableEventResolver @@ -40,6 +41,10 @@ import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.notifications.SimpleNotifiableEvent import im.vector.app.features.settings.VectorPreferences import im.vector.app.push.fcm.FcmHelper +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.pushrules.Action import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Event @@ -55,6 +60,9 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { private lateinit var pusherManager: PushersManager private lateinit var activeSessionHolder: ActiveSessionHolder private lateinit var vectorPreferences: VectorPreferences + private lateinit var wifiDetector: WifiDetector + + private val coroutineScope = CoroutineScope(SupervisorJob()) // UI handler private val mUIHandler by lazy { @@ -69,6 +77,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { pusherManager = pusherManager() activeSessionHolder = activeSessionHolder() vectorPreferences = vectorPreferences() + wifiDetector = wifiDetector() } } @@ -78,6 +87,11 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { * @param message the message */ override fun onMessageReceived(message: RemoteMessage) { + if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { + Timber.d("## onMessageReceived() %s", message.data.toString()) + } + Timber.d("## onMessageReceived() from FCM with priority %s", message.priority) + // Diagnostic Push if (message.data["event_id"] == PushersManager.TEST_EVENT_ID) { val intent = Intent(NotificationUtils.PUSH_ACTION) @@ -90,14 +104,10 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { return } - if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { - Timber.i("## onMessageReceived() %s", message.data.toString()) - Timber.i("## onMessageReceived() from FCM with priority %s", message.priority) - } mUIHandler.post { if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { // we are in foreground, let the sync do the things? - Timber.v("PUSH received in a foreground state, ignore") + Timber.d("PUSH received in a foreground state, ignore") } else { onMessageReceivedInternal(message.data) } @@ -140,7 +150,9 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { private fun onMessageReceivedInternal(data: Map) { try { if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { - Timber.i("## onMessageReceivedInternal() : $data") + Timber.d("## onMessageReceivedInternal() : $data") + } else { + Timber.d("## onMessageReceivedInternal() : $data") } // update the badge counter @@ -156,9 +168,13 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { val roomId = data["room_id"] if (isEventAlreadyKnown(eventId, roomId)) { - Timber.i("Ignoring push, event already known") + Timber.d("Ignoring push, event already known") } else { - Timber.v("Requesting background sync") + // Try to get the Event content faster + Timber.d("Requesting event in fast lane") + getEventFastLane(session, roomId, eventId) + + Timber.d("Requesting background sync") session.requireBackgroundSync() } } @@ -167,6 +183,36 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { } } + private fun getEventFastLane(session: Session, roomId: String?, eventId: String?) { + roomId?.takeIf { it.isNotEmpty() } ?: return + eventId?.takeIf { it.isNotEmpty() } ?: return + + // If the room is currently displayed, we will not show a notification, so no need to get the Event faster + if (notificationDrawerManager.shouldIgnoreMessageEventInRoom(roomId)) { + return + } + + if (wifiDetector.isConnectedToWifi().not()) { + Timber.d("No WiFi network, do not get Event") + return + } + + coroutineScope.launch { + Timber.d("Fast lane: start request") + val event = tryOrNull { session.getEvent(roomId, eventId) } ?: return@launch + + val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event) + + resolvedEvent + ?.also { Timber.d("Fast lane: notify drawer") } + ?.let { + it.isPushGatewayEvent = true + notificationDrawerManager.onNotifiableEventReceived(it) + notificationDrawerManager.refreshNotificationDrawer() + } + } + } + // check if the event was not yet received // a previous catchup might have already retrieved the notified event private fun isEventAlreadyKnown(eventId: String?, roomId: String?): Boolean { diff --git a/vector/src/main/assets/forbidden_terms.txt b/vector/src/main/assets/forbidden_terms.txt new file mode 100644 index 0000000000..84e7fe1d28 --- /dev/null +++ b/vector/src/main/assets/forbidden_terms.txt @@ -0,0 +1,68 @@ +anal +bbw +bdsm +beast +bestiality +blowjob +bondage +boobs +clit +cock +cuck +cum +cunt +daddy +dick +dildo +erotic +exhibitionism +faggot +femboy +fisting +flogging +fmf +foursome +futa +gangbang +gore +h3ntai +handjob +hentai +incest +jizz +kink +loli +m4f +masturbate +masturbation +mfm +milf +moresome +naked +neet +nsfw +nude +nudity +orgy +pedo +pegging +penis +petplay +porn +pussy +rape +rimming +sadism +sadomasochism +sexy +shota +spank +squirt +strap-on +threesome +vagina +vibrator +voyeur +watersports +xxx +zoo diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index 1e92f7bc67..edec704f18 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -19,78 +19,26 @@ package im.vector.app import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.OnLifecycleEvent -import arrow.core.Option -import im.vector.app.features.grouplist.ALL_COMMUNITIES_GROUP_ID -import im.vector.app.features.grouplist.SelectedGroupDataSource -import im.vector.app.features.home.HomeRoomListDataSource -import im.vector.app.features.home.room.list.ChronologicalRoomComparator -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable -import io.reactivex.functions.BiFunction -import io.reactivex.rxkotlin.addTo -import org.matrix.android.sdk.api.session.group.model.GroupSummary -import org.matrix.android.sdk.api.session.room.model.RoomSummary -import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams -import org.matrix.android.sdk.rx.rx -import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton /** - * This class handles the global app state. At the moment, it only manages room list. + * This class handles the global app state. * It requires to be added to ProcessLifecycleOwner.get().lifecycle */ +// TODO Keep this class for now, will maybe be used fro Space @Singleton -class AppStateHandler @Inject constructor( - private val sessionDataSource: ActiveSessionDataSource, - private val homeRoomListDataSource: HomeRoomListDataSource, - private val selectedGroupDataSource: SelectedGroupDataSource, - private val chronologicalRoomComparator: ChronologicalRoomComparator) : LifecycleObserver { +class AppStateHandler @Inject constructor() : LifecycleObserver { private val compositeDisposable = CompositeDisposable() @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun entersForeground() { - observeRoomsAndGroup() } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun entersBackground() { compositeDisposable.clear() } - - private fun observeRoomsAndGroup() { - Observable - .combineLatest, Option, List>( - sessionDataSource.observe() - .observeOn(AndroidSchedulers.mainThread()) - .switchMap { - val query = roomSummaryQueryParams {} - it.orNull()?.rx()?.liveRoomSummaries(query) - ?: Observable.just(emptyList()) - } - .throttleLast(300, TimeUnit.MILLISECONDS), - selectedGroupDataSource.observe(), - BiFunction { rooms, selectedGroupOption -> - val selectedGroup = selectedGroupOption.orNull() - val filteredRooms = rooms.filter { - if (selectedGroup == null || selectedGroup.groupId == ALL_COMMUNITIES_GROUP_ID) { - true - } else if (it.isDirect) { - it.otherMemberIds - .intersect(selectedGroup.userIds) - .isNotEmpty() - } else { - selectedGroup.roomIds.contains(it.roomId) - } - } - filteredRooms.sortedWith(chronologicalRoomComparator) - } - ) - .subscribe { - homeRoomListDataSource.post(it) - } - .addTo(compositeDisposable) - } } diff --git a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt index 23d6b618fe..4b88ff6767 100644 --- a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt @@ -26,6 +26,7 @@ import im.vector.app.EmojiCompatWrapper import im.vector.app.VectorApplication import im.vector.app.core.dialogs.UnrecognizedCertificateDialog import im.vector.app.core.error.ErrorFormatter +import im.vector.app.core.network.WifiDetector import im.vector.app.core.pushers.PushersManager import im.vector.app.core.utils.AssetReader import im.vector.app.core.utils.DimensionConverter @@ -35,7 +36,6 @@ import im.vector.app.features.crypto.keysrequest.KeyRequestHandler import im.vector.app.features.crypto.verification.IncomingVerificationRequestHandler import im.vector.app.features.grouplist.SelectedGroupDataSource import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.home.HomeRoomListDataSource import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder @@ -113,8 +113,6 @@ interface VectorComponent { fun errorFormatter(): ErrorFormatter - fun homeRoomListObservableStore(): HomeRoomListDataSource - fun selectedGroupStore(): SelectedGroupDataSource fun roomDetailPendingActionStore(): RoomDetailPendingActionStore @@ -143,6 +141,8 @@ interface VectorComponent { fun vectorPreferences(): VectorPreferences + fun wifiDetector(): WifiDetector + fun vectorFileLogger(): VectorFileLogger fun uiStateRepository(): UiStateRepository diff --git a/vector/src/main/java/im/vector/app/core/extensions/LiveData.kt b/vector/src/main/java/im/vector/app/core/extensions/LiveData.kt index 588063e2a4..5a6599acff 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/LiveData.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/LiveData.kt @@ -39,7 +39,7 @@ inline fun LiveData>.observeEventFirstThrottle(owner: Lifecycle val firstThrottler = FirstThrottler(minimumInterval) this.observe(owner, EventObserver { - if (firstThrottler.canHandle()) { + if (firstThrottler.canHandle() is FirstThrottler.CanHandlerResult.Yes) { it.run(observer) } }) diff --git a/vector/src/main/java/im/vector/app/core/network/WifiDetector.kt b/vector/src/main/java/im/vector/app/core/network/WifiDetector.kt new file mode 100644 index 0000000000..34b2a7590d --- /dev/null +++ b/vector/src/main/java/im/vector/app/core/network/WifiDetector.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.core.network + +import android.content.Context +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import android.os.Build +import androidx.core.content.getSystemService +import org.matrix.android.sdk.api.extensions.orFalse +import timber.log.Timber +import javax.inject.Inject + +class WifiDetector @Inject constructor( + context: Context +) { + private val connectivityManager = context.getSystemService()!! + + fun isConnectedToWifi(): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + connectivityManager.activeNetwork + ?.let { connectivityManager.getNetworkCapabilities(it) } + ?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + .orFalse() + } else { + @Suppress("DEPRECATION") + connectivityManager.activeNetworkInfo?.type == ConnectivityManager.TYPE_WIFI + } + .also { Timber.d("isConnected to WiFi: $it") } + } +} diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt index f515060db6..258517aa39 100644 --- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt +++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseFragment.kt @@ -127,6 +127,12 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScre Timber.i("onResume Fragment ${javaClass.simpleName}") } + @CallSuper + override fun onPause() { + super.onPause() + Timber.i("onPause Fragment ${javaClass.simpleName}") + } + @CallSuper override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -149,7 +155,9 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScre super.onDestroyView() } + @CallSuper override fun onDestroy() { + Timber.i("onDestroy Fragment ${javaClass.simpleName}") uiDisposables.dispose() super.onDestroy() } diff --git a/vector/src/main/java/im/vector/app/core/utils/FirstThrottler.kt b/vector/src/main/java/im/vector/app/core/utils/FirstThrottler.kt index 915e955fa6..004f500c4e 100644 --- a/vector/src/main/java/im/vector/app/core/utils/FirstThrottler.kt +++ b/vector/src/main/java/im/vector/app/core/utils/FirstThrottler.kt @@ -15,6 +15,8 @@ */ package im.vector.app.core.utils +import android.os.SystemClock + /** * Simple ThrottleFirst * See https://raw.githubusercontent.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleFirst.png @@ -22,14 +24,27 @@ package im.vector.app.core.utils class FirstThrottler(private val minimumInterval: Long = 800) { private var lastDate = 0L - fun canHandle(): Boolean { - val now = System.currentTimeMillis() - if (now > lastDate + minimumInterval) { + sealed class CanHandlerResult { + object Yes : CanHandlerResult() + data class No(val shouldWaitMillis: Long) : CanHandlerResult() + + fun waitMillis(): Long { + return when (this) { + Yes -> 0 + is No -> shouldWaitMillis + } + } + } + + fun canHandle(): CanHandlerResult { + val now = SystemClock.elapsedRealtime() + val delaySinceLast = now - lastDate + if (delaySinceLast > minimumInterval) { lastDate = now - return true + return CanHandlerResult.Yes } // Too soon - return false + return CanHandlerResult.No(minimumInterval - delaySinceLast) } } diff --git a/vector/src/main/java/im/vector/app/features/MainActivity.kt b/vector/src/main/java/im/vector/app/features/MainActivity.kt index e6c5abe20c..34e73c8702 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -161,25 +161,22 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity lifecycleScope.launch { try { session.signOut(!args.isUserLoggedOut) - Timber.w("SIGN_OUT: success, start app") - sessionHolder.clearActiveSession() - doLocalCleanup(clearPreferences = true) - startNextActivityAndFinish() } catch (failure: Throwable) { displayError(failure) + return@launch } + Timber.w("SIGN_OUT: success, start app") + sessionHolder.clearActiveSession() + doLocalCleanup(clearPreferences = true) + startNextActivityAndFinish() } } args.clearCache -> { lifecycleScope.launch { - try { - session.clearCache() - doLocalCleanup(clearPreferences = false) - session.startSyncing(applicationContext) - startNextActivityAndFinish() - } catch (failure: Throwable) { - displayError(failure) - } + session.clearCache() + doLocalCleanup(clearPreferences = false) + session.startSyncing(applicationContext) + startNextActivityAndFinish() } } } @@ -215,15 +212,16 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity .setTitle(R.string.dialog_title_error) .setMessage(errorFormatter.toHumanReadable(failure)) .setPositiveButton(R.string.global_retry) { _, _ -> doCleanUp() } - .setNegativeButton(R.string.cancel) { _, _ -> startNextActivityAndFinish() } + .setNegativeButton(R.string.cancel) { _, _ -> startNextActivityAndFinish(ignoreClearCredentials = true) } .setCancelable(false) .show() } } - private fun startNextActivityAndFinish() { + private fun startNextActivityAndFinish(ignoreClearCredentials: Boolean = false) { val intent = when { args.clearCredentials + && !ignoreClearCredentials && (!args.isUserLoggedOut || args.isAccountDeactivated) -> // User has explicitly asked to log out or deactivated his account LoginActivity.newIntent(this, null) diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt index fc526b5322..0b93120b52 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/IncomingVerificationRequestHandler.kt @@ -60,7 +60,7 @@ class IncomingVerificationRequestHandler @Inject constructor( // TODO maybe check also if val uid = "kvr_${tx.transactionId}" when (tx.state) { - is VerificationTxState.OnStarted -> { + is VerificationTxState.OnStarted -> { // Add a notification for every incoming request val user = session?.getUser(tx.otherUserId) val name = user?.getBestName() ?: tx.otherUserId @@ -119,6 +119,13 @@ class IncomingVerificationRequestHandler @Inject constructor( Timber.v("## SAS verificationRequestCreated ${pr.transactionId}") // For incoming request we should prompt (if not in activity where this request apply) if (pr.isIncoming) { + // if it's a self verification for my devices, we can discard the review login alert + // if not this request will be underneath and not visible by the user... + // it will re-appear later + if (pr.otherUserId == session?.myUserId) { + // XXX this is a bit hard coded :/ + popupAlertManager.cancelAlert("review_login") + } val user = session?.getUser(pr.otherUserId)?.toMatrixItem() val name = user?.getBestName() ?: pr.otherUserId val description = if (name == pr.otherUserId) { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index ad61928509..447a567cf4 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -203,9 +203,8 @@ class HomeActivityViewModel @AssistedInject constructor( _viewEvents.post( HomeActivityViewEvents.OnNewSession( session.getUser(session.myUserId)?.toMatrixItem(), - // If it's an old unverified, we should send requests - // instead of waiting for an incoming one - reAuthHelper.data != null + // Always send request instead of waiting for an incoming as per recent EW changes + false ) ) } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailAction.kt index 447820ed7b..c64f9d453d 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailAction.kt @@ -20,4 +20,5 @@ import im.vector.app.core.platform.VectorViewModelAction sealed class HomeDetailAction : VectorViewModelAction { data class SwitchDisplayMode(val displayMode: RoomListDisplayMode) : HomeDetailAction() + object MarkAllRoomsRead : HomeDetailAction() } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index 4c7b7aa991..5def43b60b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -18,6 +18,8 @@ package im.vector.app.features.home import android.os.Bundle import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.core.content.ContextCompat @@ -33,8 +35,8 @@ import im.vector.app.core.platform.ToolbarConfigurable import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.ui.views.CurrentCallsView -import im.vector.app.core.ui.views.KnownCallsViewHolder import im.vector.app.core.ui.views.KeysBackupBanner +import im.vector.app.core.ui.views.KnownCallsViewHolder import im.vector.app.databinding.FragmentHomeDetailBinding import im.vector.app.features.call.SharedKnownCallsViewModel import im.vector.app.features.call.VectorCallActivity @@ -49,7 +51,6 @@ import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.workers.signout.BannerState import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import im.vector.app.features.workers.signout.ServerBackupStatusViewState - import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo @@ -79,6 +80,32 @@ class HomeDetailFragment @Inject constructor( private lateinit var sharedActionViewModel: HomeSharedActionViewModel private lateinit var sharedCallActionViewModel: SharedKnownCallsViewModel + private var hasUnreadRooms = false + set(value) { + if (value != field) { + field = value + invalidateOptionsMenu() + } + } + + override fun getMenuRes() = R.menu.room_list + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.menu_home_mark_all_as_read -> { + viewModel.handle(HomeDetailAction.MarkAllRoomsRead) + return true + } + } + + return super.onOptionsItemSelected(item) + } + + override fun onPrepareOptionsMenu(menu: Menu) { + menu.findItem(R.id.menu_home_mark_all_as_read).isVisible = hasUnreadRooms + super.onPrepareOptionsMenu(menu) + } + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentHomeDetailBinding { return FragmentHomeDetailBinding.inflate(inflater, container, false) } @@ -314,6 +341,8 @@ class HomeDetailFragment @Inject constructor( views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_rooms).render(it.notificationCountRooms, it.notificationHighlightRooms) views.bottomNavigationView.getOrCreateBadge(R.id.bottom_action_notification).render(it.notificationCountCatchup, it.notificationHighlightCatchup) views.syncStateView.render(it.syncState) + + hasUnreadRooms = it.hasUnreadMessages } private fun BadgeDrawable.render(count: Int, highlight: Boolean) { diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index c261081055..c87b19f0e6 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -16,22 +16,30 @@ package im.vector.app.features.home +import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import im.vector.app.core.di.HasScreenInjector import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.grouplist.SelectedGroupDataSource import im.vector.app.features.ui.UiStateRepository -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.internal.util.awaitCallback +import org.matrix.android.sdk.rx.asObservable import org.matrix.android.sdk.rx.rx +import timber.log.Timber +import java.util.concurrent.TimeUnit /** * View model used to update the home bottom bar notification counts, observe the sync state and @@ -41,7 +49,6 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho private val session: Session, private val uiStateRepository: UiStateRepository, private val selectedGroupStore: SelectedGroupDataSource, - private val homeRoomListStore: HomeRoomListDataSource, private val stringProvider: StringProvider) : VectorViewModel(initialState) { @@ -75,6 +82,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho override fun handle(action: HomeDetailAction) { when (action) { is HomeDetailAction.SwitchDisplayMode -> handleSwitchDisplayMode(action) + HomeDetailAction.MarkAllRoomsRead -> handleMarkAllRoomsRead() } } @@ -90,6 +98,26 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho // PRIVATE METHODS ***************************************************************************** + private fun handleMarkAllRoomsRead() = withState { _ -> + // questionable to use viewmodelscope + viewModelScope.launch(Dispatchers.Default) { + val roomIds = session.getRoomSummaries( + roomSummaryQueryParams { + memberships = listOf(Membership.JOIN) + roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS + } + ) + .map { it.roomId } + try { + awaitCallback { + session.markAllAsRead(roomIds, it) + } + } catch (failure: Throwable) { + Timber.d(failure, "Failed to mark all as read") + } + } + } + private fun observeSyncState() { session.rx() .liveSyncState() @@ -113,43 +141,51 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho } private fun observeRoomSummaries() { - homeRoomListStore - .observe() - .observeOn(Schedulers.computation()) - .map { it.asSequence() } - .subscribe { summaries -> - val invitesDm = summaries - .filter { it.membership == Membership.INVITE && it.isDirect } - .count() + session.getPagedRoomSummariesLive( + roomSummaryQueryParams { + memberships = Membership.activeMemberships() + } + ) + .asObservable() + .throttleFirst(300, TimeUnit.MILLISECONDS) + .subscribe { + val dmInvites = session.getRoomSummaries( + roomSummaryQueryParams { + memberships = listOf(Membership.INVITE) + roomCategoryFilter = RoomCategoryFilter.ONLY_DM + } + ).size - val invitesRoom = summaries - .filter { it.membership == Membership.INVITE && it.isDirect.not() } - .count() + val roomsInvite = session.getRoomSummaries( + roomSummaryQueryParams { + memberships = listOf(Membership.INVITE) + roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + } + ).size - val peopleNotifications = summaries - .filter { it.isDirect } - .map { it.notificationCount } - .sum() - val peopleHasHighlight = summaries - .filter { it.isDirect } - .any { it.highlightCount > 0 } + val dmRooms = session.getNotificationCountForRooms( + roomSummaryQueryParams { + memberships = listOf(Membership.JOIN) + roomCategoryFilter = RoomCategoryFilter.ONLY_DM + } + ) - val roomsNotifications = summaries - .filter { !it.isDirect } - .map { it.notificationCount } - .sum() - val roomsHasHighlight = summaries - .filter { !it.isDirect } - .any { it.highlightCount > 0 } + val otherRooms = session.getNotificationCountForRooms( + roomSummaryQueryParams { + memberships = listOf(Membership.JOIN) + roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + } + ) setState { copy( - notificationCountCatchup = peopleNotifications + roomsNotifications + invitesDm + invitesRoom, - notificationHighlightCatchup = peopleHasHighlight || roomsHasHighlight, - notificationCountPeople = peopleNotifications + invitesDm, - notificationHighlightPeople = peopleHasHighlight || invitesDm > 0, - notificationCountRooms = roomsNotifications + invitesRoom, - notificationHighlightRooms = roomsHasHighlight || invitesRoom > 0 + notificationCountCatchup = dmRooms.totalCount + otherRooms.totalCount + roomsInvite + dmInvites, + notificationHighlightCatchup = dmRooms.isHighlight || otherRooms.isHighlight, + notificationCountPeople = dmRooms.totalCount + dmInvites, + notificationHighlightPeople = dmRooms.isHighlight || dmInvites > 0, + notificationCountRooms = otherRooms.totalCount + roomsInvite, + notificationHighlightRooms = otherRooms.isHighlight || roomsInvite > 0, + hasUnreadMessages = dmRooms.totalCount + otherRooms.totalCount > 0 ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt index f5e4bc9fa3..533c9166f9 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt @@ -34,5 +34,6 @@ data class HomeDetailViewState( val notificationHighlightPeople: Boolean = false, val notificationCountRooms: Int = 0, val notificationHighlightRooms: Boolean = false, + val hasUnreadMessages: Boolean = false, val syncState: SyncState = SyncState.Idle ) : MvRxState diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt index 3684a8b3f8..4a2d001e1d 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt @@ -21,36 +21,44 @@ import android.content.pm.ShortcutManager import android.os.Build import androidx.core.content.getSystemService import androidx.core.content.pm.ShortcutManagerCompat -import io.reactivex.Observable +import im.vector.app.core.di.ActiveSessionHolder import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers +import io.reactivex.disposables.Disposables +import org.matrix.android.sdk.api.query.RoomTagQueryFilter +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.rx.asObservable import javax.inject.Inject class ShortcutsHandler @Inject constructor( private val context: Context, - private val homeRoomListStore: HomeRoomListDataSource, - private val shortcutCreator: ShortcutCreator + private val shortcutCreator: ShortcutCreator, + private val activeSessionHolder: ActiveSessionHolder ) { fun observeRoomsAndBuildShortcuts(): Disposable { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { // No op - return Observable.empty().subscribe() + return Disposables.empty() } - return homeRoomListStore - .observe() - .distinctUntilChanged() - .observeOn(Schedulers.computation()) - .subscribe { rooms -> + return activeSessionHolder.getSafeActiveSession() + ?.getPagedRoomSummariesLive( + roomSummaryQueryParams { + memberships = listOf(Membership.JOIN) + roomTagQueryFilter = RoomTagQueryFilter(isFavorite = true, null, null) + } + ) + ?.asObservable() + ?.subscribe { rooms -> val shortcuts = rooms - .filter { room -> room.isFavorite } .take(n = 4) // Android only allows us to create 4 shortcuts .map { shortcutCreator.create(it) } ShortcutManagerCompat.removeAllDynamicShortcuts(context) ShortcutManagerCompat.addDynamicShortcuts(context, shortcuts) } + ?: Disposables.empty() } fun clearShortcuts() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index b2e7004d0f..b7e2e189d3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1675,10 +1675,12 @@ class RoomDetailFragment @Inject constructor( shareText(requireContext(), action.messageContent.body) } else if (action.messageContent is MessageWithAttachmentContent) { lifecycleScope.launch { - val data = session.fileService().downloadFile(messageContent = action.messageContent) - if (isAdded) { - shareMedia(requireContext(), data, getMimeTypeFromUri(requireContext(), data.toUri())) - } + val result = runCatching { session.fileService().downloadFile(messageContent = action.messageContent) } + if (!isAdded) return@launch + result.fold( + { shareMedia(requireContext(), it, getMimeTypeFromUri(requireContext(), it.toUri())) }, + { showErrorInSnackbar(it) } + ) } } } @@ -1701,16 +1703,22 @@ class RoomDetailFragment @Inject constructor( return } lifecycleScope.launch { - val data = session.fileService().downloadFile(messageContent = action.messageContent) - if (isAdded) { - saveMedia( - context = requireContext(), - file = data, - title = action.messageContent.body, - mediaMimeType = action.messageContent.mimeType ?: getMimeTypeFromUri(requireContext(), data.toUri()), - notificationUtils = notificationUtils - ) - } + val result = runCatching { session.fileService().downloadFile(messageContent = action.messageContent) } + if (!isAdded) return@launch + result.fold( + { + saveMedia( + context = requireContext(), + file = it, + title = action.messageContent.body, + mediaMimeType = action.messageContent.mimeType ?: getMimeTypeFromUri(requireContext(), it.toUri()), + notificationUtils = notificationUtils + ) + }, + { + showErrorInSnackbar(it) + } + ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt index 4a6c1c16fc..883efb2e60 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt @@ -22,12 +22,11 @@ import org.matrix.android.sdk.api.session.room.notification.RoomNotificationStat sealed class RoomListAction : VectorViewModelAction { data class SelectRoom(val roomSummary: RoomSummary) : RoomListAction() - data class ToggleCategory(val category: RoomCategory) : RoomListAction() + data class ToggleSection(val section: RoomsSection) : RoomListAction() data class AcceptInvitation(val roomSummary: RoomSummary) : RoomListAction() data class RejectInvitation(val roomSummary: RoomSummary) : RoomListAction() data class FilterWith(val filter: String) : RoomListAction() data class ChangeRoomNotificationState(val roomId: String, val notificationState: RoomNotificationState) : RoomListAction() data class ToggleTag(val roomId: String, val tag: String) : RoomListAction() data class LeaveRoom(val roomId: String) : RoomListAction() - object MarkAllRoomsRead : RoomListAction() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFooterController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFooterController.kt new file mode 100644 index 0000000000..d4e062d1e4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFooterController.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list + +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.app.R +import im.vector.app.core.epoxy.helpFooterItem +import im.vector.app.core.resources.StringProvider +import im.vector.app.core.resources.UserPreferencesProvider +import im.vector.app.features.home.RoomListDisplayMode +import im.vector.app.features.home.room.filtered.filteredRoomFooterItem +import javax.inject.Inject + +class RoomListFooterController @Inject constructor( + private val stringProvider: StringProvider, + private val userPreferencesProvider: UserPreferencesProvider +) : TypedEpoxyController() { + + var listener: RoomListListener? = null + + override fun buildModels(data: RoomListViewState?) { + when (data?.displayMode) { + RoomListDisplayMode.FILTERED -> { + filteredRoomFooterItem { + id("filter_footer") + listener(listener) + currentFilter(data.roomFilter) + } + } + else -> { + if (userPreferencesProvider.shouldShowLongClickOnRoomHelp()) { + helpFooterItem { + id("long_click_help") + text(stringProvider.getString(R.string.help_long_click_on_room_for_more_options)) + } + } + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index 30cb360a9d..aaa5bbcde5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -20,19 +20,15 @@ import android.content.DialogInterface import android.os.Bundle import android.os.Parcelable import android.view.LayoutInflater -import android.view.Menu -import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.core.view.isVisible +import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.airbnb.epoxy.OnModelBuildFinishedListener -import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.Incomplete -import com.airbnb.mvrx.Success import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -44,6 +40,7 @@ import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.StateView import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.databinding.FragmentRoomListBinding import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.home.room.list.actions.RoomListActionsArgs @@ -53,8 +50,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA import im.vector.app.features.home.room.list.widget.NotifsFabMenuView import im.vector.app.features.notifications.NotificationDrawerManager import kotlinx.parcelize.Parcelize -import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState @@ -66,12 +62,13 @@ data class RoomListParams( ) : Parcelable class RoomListFragment @Inject constructor( - private val roomController: RoomSummaryController, + private val pagedControllerFactory: RoomSummaryPagedControllerFactory, val roomListViewModelFactory: RoomListViewModel.Factory, private val notificationDrawerManager: NotificationDrawerManager, - private val sharedViewPool: RecyclerView.RecycledViewPool + private val footerController: RoomListFooterController, + private val userPreferencesProvider: UserPreferencesProvider ) : VectorBaseFragment(), - RoomSummaryController.Listener, + RoomListListener, OnBackPressed, NotifsFabMenuView.Listener { @@ -85,28 +82,25 @@ class RoomListFragment @Inject constructor( return FragmentRoomListBinding.inflate(inflater, container, false) } - private var hasUnreadRooms = false + data class SectionKey( + val name: String, + val isExpanded: Boolean, + val notifyOfLocalEcho: Boolean + ) - override fun getMenuRes() = R.menu.room_list + data class SectionAdapterInfo( + var section: SectionKey, + val headerHeaderAdapter: SectionHeaderAdapter, + val contentAdapter: RoomSummaryPagedController + ) - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.menu_home_mark_all_as_read -> { - roomListViewModel.handle(RoomListAction.MarkAllRoomsRead) - return true - } - } - - return super.onOptionsItemSelected(item) - } - - override fun onPrepareOptionsMenu(menu: Menu) { - menu.findItem(R.id.menu_home_mark_all_as_read).isVisible = hasUnreadRooms - super.onPrepareOptionsMenu(menu) - } + private val adapterInfosList = mutableListOf() + private var concatAdapter : ConcatAdapter? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + views.stateView.contentView = views.roomListView + views.stateView.state = StateView.State.Loading setupCreateRoomButton() setupRecyclerView() sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) @@ -125,6 +119,40 @@ class RoomListFragment @Inject constructor( .observe() .subscribe { handleQuickActions(it) } .disposeOnDestroyView() + + roomListViewModel.selectSubscribe(viewLifecycleOwner, RoomListViewState::roomMembershipChanges) { ms -> + // it's for invites local echo + adapterInfosList.filter { it.section.notifyOfLocalEcho } + .onEach { + it.contentAdapter.roomChangeMembershipStates = ms + } + } + } + + private fun refreshCollapseStates() { + var contentInsertIndex = 1 + roomListViewModel.sections.forEachIndexed { index, roomsSection -> + val actualBlock = adapterInfosList[index] + val isRoomSectionExpanded = roomsSection.isExpanded.value.orTrue() + if (actualBlock.section.isExpanded && !isRoomSectionExpanded) { + // we have to remove the content adapter + concatAdapter?.removeAdapter(actualBlock.contentAdapter.adapter) + } else if (!actualBlock.section.isExpanded && isRoomSectionExpanded) { + // we must add it back! + concatAdapter?.addAdapter(contentInsertIndex, actualBlock.contentAdapter.adapter) + } + contentInsertIndex = if (isRoomSectionExpanded) { + contentInsertIndex + 2 + } else { + contentInsertIndex + 1 + } + actualBlock.section = actualBlock.section.copy( + isExpanded = isRoomSectionExpanded + ) + actualBlock.headerHeaderAdapter.updateSection( + actualBlock.headerHeaderAdapter.roomsSectionData.copy(isExpanded = isRoomSectionExpanded) + ) + } } override fun showFailure(throwable: Throwable) { @@ -132,12 +160,15 @@ class RoomListFragment @Inject constructor( } override fun onDestroyView() { - roomController.removeModelBuildListener(modelBuildListener) + adapterInfosList.onEach { it.contentAdapter.removeModelBuildListener(modelBuildListener) } + adapterInfosList.clear() modelBuildListener = null views.roomListView.cleanup() - roomController.listener = null + footerController.listener = null + // TODO Cleanup listener on the ConcatAdapter's adapters? stateRestorer.clear() views.createChatFabMenu.listener = null + concatAdapter = null super.onDestroyView() } @@ -204,13 +235,58 @@ class RoomListFragment @Inject constructor( stateRestorer = LayoutManagerStateRestorer(layoutManager).register() views.roomListView.layoutManager = layoutManager views.roomListView.itemAnimator = RoomListAnimator() - views.roomListView.setRecycledViewPool(sharedViewPool) layoutManager.recycleChildrenOnDetach = true - roomController.listener = this + modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) } - roomController.addModelBuildListener(modelBuildListener) - views.roomListView.adapter = roomController.adapter - views.stateView.contentView = views.roomListView + + val concatAdapter = ConcatAdapter() + + roomListViewModel.sections.forEach { section -> + val sectionAdapter = SectionHeaderAdapter { + roomListViewModel.handle(RoomListAction.ToggleSection(section)) + }.also { + it.updateSection(SectionHeaderAdapter.RoomsSectionData(section.sectionName)) + } + + val contentAdapter = pagedControllerFactory.createRoomSummaryPagedController() + .also { controller -> + section.livePages.observe(viewLifecycleOwner) { pl -> + controller.submitList(pl) + sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy(isHidden = pl.isEmpty())) + checkEmptyState() + } + section.notificationCount.observe(viewLifecycleOwner) { counts -> + sectionAdapter.updateSection(sectionAdapter.roomsSectionData.copy( + notificationCount = counts.totalCount, + isHighlighted = counts.isHighlight + )) + } + section.isExpanded.observe(viewLifecycleOwner) { _ -> + refreshCollapseStates() + } + controller.listener = this + } + adapterInfosList.add( + SectionAdapterInfo( + SectionKey( + name = section.sectionName, + isExpanded = section.isExpanded.value.orTrue(), + notifyOfLocalEcho = section.notifyOfLocalEcho + ), + sectionAdapter, + contentAdapter + ) + ) + concatAdapter.addAdapter(sectionAdapter) + concatAdapter.addAdapter(contentAdapter.adapter) + } + + // Add the footer controller + footerController.listener = this + concatAdapter.addAdapter(footerController.adapter) + + this.concatAdapter = concatAdapter + views.roomListView.adapter = concatAdapter } private val showFabRunnable = Runnable { @@ -278,89 +354,41 @@ class RoomListFragment @Inject constructor( } override fun invalidate() = withState(roomListViewModel) { state -> - when (state.asyncFilteredRooms) { - is Incomplete -> renderLoading() - is Success -> renderSuccess(state) - is Fail -> renderFailure(state.asyncFilteredRooms.error) - } - roomController.update(state) - // Mark all as read menu - when (roomListParams.displayMode) { - RoomListDisplayMode.NOTIFICATIONS, - RoomListDisplayMode.PEOPLE, - RoomListDisplayMode.ROOMS -> { - val newValue = state.hasUnread - if (hasUnreadRooms != newValue) { - hasUnreadRooms = newValue - invalidateOptionsMenu() - } - } - else -> Unit - } + footerController.setData(state) } - private fun renderSuccess(state: RoomListViewState) { - val allRooms = state.asyncRooms() - val filteredRooms = state.asyncFilteredRooms() - if (filteredRooms.isNullOrEmpty()) { - renderEmptyState(allRooms) - } else { - views.stateView.state = StateView.State.Content - } - } - - private fun renderEmptyState(allRooms: List?) { - val hasNoRoom = allRooms - ?.filter { - it.membership == Membership.JOIN || it.membership == Membership.INVITE - } - .isNullOrEmpty() - val emptyState = when (roomListParams.displayMode) { - RoomListDisplayMode.NOTIFICATIONS -> { - if (hasNoRoom) { - StateView.State.Empty( - title = getString(R.string.room_list_catchup_welcome_title), - image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_home_bottom_catchup), - message = getString(R.string.room_list_catchup_welcome_body) - ) - } else { + private fun checkEmptyState() { + val hasNoRoom = adapterInfosList.all { it.headerHeaderAdapter.roomsSectionData.isHidden } + if (hasNoRoom) { + val emptyState = when (roomListParams.displayMode) { + RoomListDisplayMode.NOTIFICATIONS -> { StateView.State.Empty( title = getString(R.string.room_list_catchup_empty_title), image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_noun_party_popper), message = getString(R.string.room_list_catchup_empty_body)) } + RoomListDisplayMode.PEOPLE -> + StateView.State.Empty( + title = getString(R.string.room_list_people_empty_title), + image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_dm), + isBigImage = true, + message = getString(R.string.room_list_people_empty_body) + ) + RoomListDisplayMode.ROOMS -> + StateView.State.Empty( + title = getString(R.string.room_list_rooms_empty_title), + image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_room), + isBigImage = true, + message = getString(R.string.room_list_rooms_empty_body) + ) + else -> + // Always display the content in this mode, because if the footer + StateView.State.Content } - RoomListDisplayMode.PEOPLE -> - StateView.State.Empty( - title = getString(R.string.room_list_people_empty_title), - image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_dm), - isBigImage = true, - message = getString(R.string.room_list_people_empty_body) - ) - RoomListDisplayMode.ROOMS -> - StateView.State.Empty( - title = getString(R.string.room_list_rooms_empty_title), - image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_room), - isBigImage = true, - message = getString(R.string.room_list_rooms_empty_body) - ) - else -> - // Always display the content in this mode, because if the footer - StateView.State.Content + views.stateView.state = emptyState + } else { + views.stateView.state = StateView.State.Content } - views.stateView.state = emptyState - } - - private fun renderLoading() { - views.stateView.state = StateView.State.Loading - } - - private fun renderFailure(error: Throwable) { - val message = when (error) { - is Failure.NetworkConnection -> getString(R.string.network_error_please_check_and_retry) - else -> getString(R.string.unknown_error) - } - views.stateView.state = StateView.State.Error(message) } override fun onBackPressed(toolbarButton: Boolean): Boolean { @@ -377,7 +405,11 @@ class RoomListFragment @Inject constructor( } override fun onRoomLongClicked(room: RoomSummary): Boolean { - roomController.onRoomLongClicked() + userPreferencesProvider.neverShowLongClickOnRoomHelpAgain() + withState(roomListViewModel) { + // refresh footer + footerController.setData(it) + } RoomListQuickActionsBottomSheet .newInstance(room.roomId, RoomListActionsArgs.Mode.FULL) .show(childFragmentManager, "ROOM_LIST_QUICK_ACTIONS") @@ -394,10 +426,6 @@ class RoomListFragment @Inject constructor( roomListViewModel.handle(RoomListAction.RejectInvitation(room)) } - override fun onToggleRoomCategory(roomCategory: RoomCategory) { - roomListViewModel.handle(RoomListAction.ToggleCategory(roomCategory)) - } - override fun createRoom(initialName: String) { navigator.openCreateRoom(requireActivity(), initialName) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt new file mode 100644 index 0000000000..e9833d1560 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list + +import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem +import org.matrix.android.sdk.api.session.room.model.RoomSummary + +interface RoomListListener : FilteredRoomFooterItem.FilteredRoomFooterItemListener { + fun onRoomClicked(room: RoomSummary) + fun onRoomLongClicked(room: RoomSummary): Boolean + fun onRejectRoomInvitation(room: RoomSummary) + fun onAcceptRoomInvitation(room: RoomSummary) +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index f4cfbe94d0..423a950591 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -16,36 +16,61 @@ package im.vector.app.features.home.room.list +import androidx.annotation.StringRes import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext +import im.vector.app.R import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.utils.DataSource +import im.vector.app.core.resources.StringProvider +import im.vector.app.features.home.RoomListDisplayMode import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.RoomCategoryFilter +import org.matrix.android.sdk.api.query.RoomTagQueryFilter import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams +import org.matrix.android.sdk.api.session.room.UpdatableFilterLivePageResult +import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.tag.RoomTag +import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.state.isPublic +import org.matrix.android.sdk.rx.asObservable import org.matrix.android.sdk.rx.rx import timber.log.Timber -import java.lang.Exception import javax.inject.Inject -class RoomListViewModel @Inject constructor(initialState: RoomListViewState, - private val session: Session, - private val roomSummariesSource: DataSource>) - : VectorViewModel(initialState) { +class RoomListViewModel @Inject constructor( + initialState: RoomListViewState, + private val session: Session, + private val stringProvider: StringProvider +) : VectorViewModel(initialState) { interface Factory { fun create(initialState: RoomListViewState): RoomListViewModel } + private var updatableQuery: UpdatableFilterLivePageResult? = null + + init { + observeMembershipChanges() + } + + private fun observeMembershipChanges() { + session.rx() + .liveRoomChangeMembershipState() + .subscribe { + setState { copy(roomMembershipChanges = it) } + } + .disposeOnClear() + } + companion object : MvRxViewModelFactory { @JvmStatic @@ -55,28 +80,136 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } } - private val displayMode = initialState.displayMode - private val roomListDisplayModeFilter = RoomListDisplayModeFilter(displayMode) + val sections: List by lazy { + val sections = mutableListOf() + if (initialState.displayMode == RoomListDisplayMode.PEOPLE) { + addSection(sections, R.string.invitations_header, true) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM + } - init { - observeRoomSummaries() - observeMembershipChanges() + addSection(sections, R.string.bottom_action_favourites) { + it.memberships = listOf(Membership.JOIN) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM + it.roomTagQueryFilter = RoomTagQueryFilter(true, null, null) + } + + addSection(sections, R.string.bottom_action_people_x) { + it.memberships = listOf(Membership.JOIN) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM + } + } else if (initialState.displayMode == RoomListDisplayMode.ROOMS) { + addSection(sections, R.string.invitations_header, true) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + } + + addSection(sections, R.string.bottom_action_favourites) { + it.memberships = listOf(Membership.JOIN) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + it.roomTagQueryFilter = RoomTagQueryFilter(true, null, null) + } + + addSection(sections, R.string.bottom_action_rooms) { + it.memberships = listOf(Membership.JOIN) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + it.roomTagQueryFilter = RoomTagQueryFilter(false, false, false) + } + + addSection(sections, R.string.low_priority_header) { + it.memberships = listOf(Membership.JOIN) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + it.roomTagQueryFilter = RoomTagQueryFilter(null, true, null) + } + + addSection(sections, R.string.system_alerts_header) { + it.memberships = listOf(Membership.JOIN) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + it.roomTagQueryFilter = RoomTagQueryFilter(null, null, true) + } + } else if (initialState.displayMode == RoomListDisplayMode.FILTERED) { + withQueryParams( + { + it.memberships = Membership.activeMemberships() + }, + { qpm -> + val name = stringProvider.getString(R.string.bottom_action_rooms) + session.getFilteredPagedRoomSummariesLive(qpm) + .let { updatableFilterLivePageResult -> + updatableQuery = updatableFilterLivePageResult + sections.add(RoomsSection(name, updatableFilterLivePageResult.livePagedList)) + } + } + ) + } else if (initialState.displayMode == RoomListDisplayMode.NOTIFICATIONS) { + addSection(sections, R.string.invitations_header, true) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ALL + } + + addSection(sections, R.string.bottom_action_rooms, true) { + it.memberships = listOf(Membership.JOIN) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS + } + } + + sections } override fun handle(action: RoomListAction) { when (action) { is RoomListAction.SelectRoom -> handleSelectRoom(action) - is RoomListAction.ToggleCategory -> handleToggleCategory(action) is RoomListAction.AcceptInvitation -> handleAcceptInvitation(action) is RoomListAction.RejectInvitation -> handleRejectInvitation(action) is RoomListAction.FilterWith -> handleFilter(action) - is RoomListAction.MarkAllRoomsRead -> handleMarkAllRoomsRead() is RoomListAction.LeaveRoom -> handleLeaveRoom(action) is RoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action) is RoomListAction.ToggleTag -> handleToggleTag(action) + is RoomListAction.ToggleSection -> handleToggleSection(action.section) }.exhaustive } + private fun addSection(sections: MutableList, + @StringRes nameRes: Int, + notifyOfLocalEcho: Boolean = false, + query: (RoomSummaryQueryParams.Builder) -> Unit) { + withQueryParams( + { query.invoke(it) }, + { roomQueryParams -> + + val name = stringProvider.getString(nameRes) + session.getPagedRoomSummariesLive(roomQueryParams) + .let { livePagedList -> + + // use it also as a source to update count + livePagedList.asObservable() + .observeOn(Schedulers.computation()) + .subscribe { + sections.find { it.sectionName == name } + ?.notificationCount + ?.postValue(session.getNotificationCountForRooms(roomQueryParams)) + } + .disposeOnClear() + + sections.add( + RoomsSection( + sectionName = name, + livePages = livePagedList, + notifyOfLocalEcho = notifyOfLocalEcho + ) + ) + } + } + ) + } + + private fun withQueryParams(builder: (RoomSummaryQueryParams.Builder) -> Unit, block: (RoomSummaryQueryParams) -> Unit) { + RoomSummaryQueryParams.Builder() + .apply { builder.invoke(this) } + .build() + .let { block(it) } + } + fun isPublicRoom(roomId: String): Boolean { return session.getRoom(roomId)?.isPublic().orFalse() } @@ -87,8 +220,14 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, _viewEvents.post(RoomListViewEvents.SelectRoom(action.roomSummary)) } - private fun handleToggleCategory(action: RoomListAction.ToggleCategory) = setState { - this.toggle(action.category) + private fun handleToggleSection(roomSection: RoomsSection) { + roomSection.isExpanded.postValue(!roomSection.isExpanded.value.orFalse()) + /* TODO Cleanup if it is working + sections.find { it.sectionName == roomSection.sectionName } + ?.let { section -> + section.isExpanded.postValue(!section.isExpanded.value.orFalse()) + } + */ } private fun handleFilter(action: RoomListAction.FilterWith) { @@ -97,23 +236,12 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, roomFilter = action.filter ) } - } - - private fun observeRoomSummaries() { - roomSummariesSource - .observe() - .observeOn(Schedulers.computation()) - .execute { asyncRooms -> - copy(asyncRooms = asyncRooms) - } - - roomSummariesSource - .observe() - .observeOn(Schedulers.computation()) - .map { buildRoomSummaries(it) } - .execute { async -> - copy(asyncFilteredRooms = async) + updatableQuery?.updateQuery( + roomSummaryQueryParams { + memberships = Membership.activeMemberships() + displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) } + ) } private fun handleAcceptInvitation(action: RoomListAction.AcceptInvitation) = withState { state -> @@ -125,6 +253,19 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, return@withState } + // quick echo + setState { + copy( + roomMembershipChanges = roomMembershipChanges.mapValues { + if (it.key == roomId) { + ChangeMembershipState.Joining + } else { + it.value + } + } + ) + } + val room = session.getRoom(roomId) ?: return@withState viewModelScope.launch { try { @@ -162,22 +303,6 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } } - private fun handleMarkAllRoomsRead() = withState { state -> - state.asyncFilteredRooms.invoke() - ?.flatMap { it.value } - ?.filter { it.membership == Membership.JOIN } - ?.map { it.roomId } - ?.toList() - ?.let { - viewModelScope.launch { - try { - session.markAllAsRead(it) - } catch (_: Exception) { - } - } - } - } - private fun handleChangeNotificationMode(action: RoomListAction.ChangeRoomNotificationState) { val room = session.getRoom(action.roomId) if (room != null) { @@ -232,46 +357,4 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, _viewEvents.post(value) } } - - private fun observeMembershipChanges() { - session.rx() - .liveRoomChangeMembershipState() - .subscribe { - Timber.v("ChangeMembership states: $it") - setState { copy(roomMembershipChanges = it) } - } - .disposeOnClear() - } - - private fun buildRoomSummaries(rooms: List): RoomSummaries { - // Set up init size on directChats and groupRooms as they are the biggest ones - val invites = ArrayList() - val favourites = ArrayList() - val directChats = ArrayList(rooms.size) - val groupRooms = ArrayList(rooms.size) - val lowPriorities = ArrayList() - val serverNotices = ArrayList() - - rooms - .filter { roomListDisplayModeFilter.test(it) } - .forEach { room -> - val tags = room.tags.map { it.name } - when { - room.membership == Membership.INVITE -> invites.add(room) - tags.contains(RoomTag.ROOM_TAG_SERVER_NOTICE) -> serverNotices.add(room) - tags.contains(RoomTag.ROOM_TAG_FAVOURITE) -> favourites.add(room) - tags.contains(RoomTag.ROOM_TAG_LOW_PRIORITY) -> lowPriorities.add(room) - room.isDirect -> directChats.add(room) - else -> groupRooms.add(room) - } - } - return RoomSummaries().apply { - put(RoomCategory.INVITE, invites) - put(RoomCategory.FAVOURITE, favourites) - put(RoomCategory.DIRECT, directChats) - put(RoomCategory.GROUP, groupRooms) - put(RoomCategory.LOW_PRIORITY, lowPriorities) - put(RoomCategory.SERVER_NOTICE, serverNotices) - } - } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt index 44ca8cefda..d36bc45ab6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt @@ -16,20 +16,20 @@ package im.vector.app.features.home.room.list -import im.vector.app.features.home.HomeRoomListDataSource +import im.vector.app.core.resources.StringProvider import org.matrix.android.sdk.api.session.Session import javax.inject.Inject import javax.inject.Provider class RoomListViewModelFactory @Inject constructor(private val session: Provider, - private val homeRoomListDataSource: Provider) + private val stringProvider: StringProvider) : RoomListViewModel.Factory { override fun create(initialState: RoomListViewState): RoomListViewModel { return RoomListViewModel( initialState, session.get(), - homeRoomListDataSource.get() + stringProvider ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt index 095262d74b..104a3710f7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt @@ -16,73 +16,15 @@ package im.vector.app.features.home.room.list -import androidx.annotation.StringRes -import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState -import com.airbnb.mvrx.Uninitialized -import im.vector.app.R import im.vector.app.features.home.RoomListDisplayMode import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState -import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.RoomSummary data class RoomListViewState( val displayMode: RoomListDisplayMode, - val asyncRooms: Async> = Uninitialized, val roomFilter: String = "", - val asyncFilteredRooms: Async = Uninitialized, - val roomMembershipChanges: Map = emptyMap(), - val isInviteExpanded: Boolean = true, - val isFavouriteRoomsExpanded: Boolean = true, - val isDirectRoomsExpanded: Boolean = true, - val isGroupRoomsExpanded: Boolean = true, - val isLowPriorityRoomsExpanded: Boolean = true, - val isServerNoticeRoomsExpanded: Boolean = true + val roomMembershipChanges: Map = emptyMap() ) : MvRxState { constructor(args: RoomListParams) : this(displayMode = args.displayMode) - - fun isCategoryExpanded(roomCategory: RoomCategory): Boolean { - return when (roomCategory) { - RoomCategory.INVITE -> isInviteExpanded - RoomCategory.FAVOURITE -> isFavouriteRoomsExpanded - RoomCategory.DIRECT -> isDirectRoomsExpanded - RoomCategory.GROUP -> isGroupRoomsExpanded - RoomCategory.LOW_PRIORITY -> isLowPriorityRoomsExpanded - RoomCategory.SERVER_NOTICE -> isServerNoticeRoomsExpanded - } - } - - fun toggle(roomCategory: RoomCategory): RoomListViewState { - return when (roomCategory) { - RoomCategory.INVITE -> copy(isInviteExpanded = !isInviteExpanded) - RoomCategory.FAVOURITE -> copy(isFavouriteRoomsExpanded = !isFavouriteRoomsExpanded) - RoomCategory.DIRECT -> copy(isDirectRoomsExpanded = !isDirectRoomsExpanded) - RoomCategory.GROUP -> copy(isGroupRoomsExpanded = !isGroupRoomsExpanded) - RoomCategory.LOW_PRIORITY -> copy(isLowPriorityRoomsExpanded = !isLowPriorityRoomsExpanded) - RoomCategory.SERVER_NOTICE -> copy(isServerNoticeRoomsExpanded = !isServerNoticeRoomsExpanded) - } - } - - val hasUnread: Boolean - get() = asyncFilteredRooms.invoke() - ?.flatMap { it.value } - ?.filter { it.membership == Membership.JOIN } - ?.any { it.hasUnreadMessages } - ?: false -} - -typealias RoomSummaries = LinkedHashMap> - -enum class RoomCategory(@StringRes val titleRes: Int) { - INVITE(R.string.invitations_header), - FAVOURITE(R.string.bottom_action_favourites), - DIRECT(R.string.bottom_action_people_x), - GROUP(R.string.bottom_action_rooms), - LOW_PRIORITY(R.string.low_priority_header), - SERVER_NOTICE(R.string.system_alerts_header) -} - -fun RoomSummaries?.isNullOrEmpty(): Boolean { - return this == null || this.values.flatten().isEmpty() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryController.kt deleted file mode 100644 index d7cace9edb..0000000000 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryController.kt +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2019 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.home.room.list - -import androidx.annotation.StringRes -import com.airbnb.epoxy.EpoxyController -import im.vector.app.R -import im.vector.app.core.epoxy.helpFooterItem -import im.vector.app.core.resources.StringProvider -import im.vector.app.core.resources.UserPreferencesProvider -import im.vector.app.features.home.RoomListDisplayMode -import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem -import im.vector.app.features.home.room.filtered.filteredRoomFooterItem -import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState -import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.RoomSummary -import javax.inject.Inject - -class RoomSummaryController @Inject constructor(private val stringProvider: StringProvider, - private val roomSummaryItemFactory: RoomSummaryItemFactory, - private val roomListNameFilter: RoomListNameFilter, - private val userPreferencesProvider: UserPreferencesProvider -) : EpoxyController() { - - var listener: Listener? = null - - private var viewState: RoomListViewState? = null - - init { - // We are requesting a model build directly as the first build of epoxy is on the main thread. - // It avoids to build the whole list of rooms on the main thread. - requestModelBuild() - } - - fun update(viewState: RoomListViewState) { - this.viewState = viewState - requestModelBuild() - } - - fun onRoomLongClicked() { - userPreferencesProvider.neverShowLongClickOnRoomHelpAgain() - requestModelBuild() - } - - override fun buildModels() { - val nonNullViewState = viewState ?: return - when (nonNullViewState.displayMode) { - RoomListDisplayMode.FILTERED -> buildFilteredRooms(nonNullViewState) - else -> buildRooms(nonNullViewState) - } - } - - private fun buildFilteredRooms(viewState: RoomListViewState) { - val summaries = viewState.asyncRooms() ?: return - - roomListNameFilter.filter = viewState.roomFilter - - val filteredSummaries = summaries - .filter { it.membership == Membership.JOIN && roomListNameFilter.test(it) } - - buildRoomModels(filteredSummaries, - viewState.roomMembershipChanges, - emptySet()) - - addFilterFooter(viewState) - } - - private fun buildRooms(viewState: RoomListViewState) { - var showHelp = false - val roomSummaries = viewState.asyncFilteredRooms() - roomSummaries?.forEach { (category, summaries) -> - if (summaries.isEmpty()) { - return@forEach - } else { - val isExpanded = viewState.isCategoryExpanded(category) - buildRoomCategory(viewState, summaries, category.titleRes, viewState.isCategoryExpanded(category)) { - listener?.onToggleRoomCategory(category) - } - if (isExpanded) { - buildRoomModels(summaries, - viewState.roomMembershipChanges, - emptySet()) - // Never set showHelp to true for invitation - if (category != RoomCategory.INVITE) { - showHelp = userPreferencesProvider.shouldShowLongClickOnRoomHelp() - } - } - } - } - - if (showHelp) { - buildLongClickHelp() - } - } - - private fun buildLongClickHelp() { - helpFooterItem { - id("long_click_help") - text(stringProvider.getString(R.string.help_long_click_on_room_for_more_options)) - } - } - - private fun addFilterFooter(viewState: RoomListViewState) { - filteredRoomFooterItem { - id("filter_footer") - listener(listener) - currentFilter(viewState.roomFilter) - } - } - - private fun buildRoomCategory(viewState: RoomListViewState, - summaries: List, - @StringRes titleRes: Int, - isExpanded: Boolean, - mutateExpandedState: () -> Unit) { - // TODO should add some business logic later - val unreadCount = if (summaries.isEmpty()) { - 0 - } else { - summaries.map { it.notificationCount }.sumBy { i -> i } - } - val showHighlighted = summaries.any { it.highlightCount > 0 } - roomCategoryItem { - id(titleRes) - title(stringProvider.getString(titleRes)) - expanded(isExpanded) - unreadNotificationCount(unreadCount) - showHighlighted(showHighlighted) - listener { - mutateExpandedState() - update(viewState) - } - } - } - - private fun buildRoomModels(summaries: List, - roomChangedMembershipStates: Map, - selectedRoomIds: Set) { - summaries.forEach { roomSummary -> - roomSummaryItemFactory - .create(roomSummary, - roomChangedMembershipStates, - selectedRoomIds, - listener) - .addTo(this) - } - } - - interface Listener : FilteredRoomFooterItem.FilteredRoomFooterItemListener { - fun onToggleRoomCategory(roomCategory: RoomCategory) - fun onRoomClicked(room: RoomSummary) - fun onRoomLongClicked(room: RoomSummary): Boolean - fun onRejectRoomInvitation(room: RoomSummary) - fun onAcceptRoomInvitation(room: RoomSummary) - } -} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt index 7d7ed1637f..fa6c970d8a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt @@ -40,7 +40,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor fun create(roomSummary: RoomSummary, roomChangeMembershipStates: Map, selectedRoomIds: Set, - listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> { + listener: RoomListListener?): VectorEpoxyModel<*> { return when (roomSummary.membership) { Membership.INVITE -> { val changeMembershipState = roomChangeMembershipStates[roomSummary.roomId] ?: ChangeMembershipState.Unknown @@ -52,7 +52,7 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor private fun createInvitationItem(roomSummary: RoomSummary, changeMembershipState: ChangeMembershipState, - listener: RoomSummaryController.Listener?): VectorEpoxyModel<*> { + listener: RoomListListener?): VectorEpoxyModel<*> { val secondLine = if (roomSummary.isDirect) { roomSummary.inviterId } else { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt new file mode 100644 index 0000000000..20386d739a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list + +import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.paging.PagedListEpoxyController +import im.vector.app.core.utils.createUIHandler +import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import javax.inject.Inject + +class RoomSummaryPagedControllerFactory @Inject constructor( + private val roomSummaryItemFactory: RoomSummaryItemFactory +) { + + fun createRoomSummaryPagedController(): RoomSummaryPagedController { + return RoomSummaryPagedController(roomSummaryItemFactory) + } +} + +class RoomSummaryPagedController( + private val roomSummaryItemFactory: RoomSummaryItemFactory +) : PagedListEpoxyController( + // Important it must match the PageList builder notify Looper + modelBuildingHandler = createUIHandler() +) { + + var listener: RoomListListener? = null + + var roomChangeMembershipStates: Map? = null + set(value) { + field = value + // ideally we could search for visible models and update only those + requestForcedModelBuild() + } + + override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> { + // for place holder if enabled + item ?: return roomSummaryItemFactory.createRoomItem( + roomSummary = RoomSummary( + roomId = "null_item_pos_$currentPosition", + name = "", + encryptionEventTs = null, + isEncrypted = false, + typingUsers = emptyList() + ), + selectedRoomIds = emptySet(), + onClick = null, + onLongClick = null + ) + + return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), listener) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt new file mode 100644 index 0000000000..71b7169814 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.paging.PagedList +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount + +data class RoomsSection( + val sectionName: String, + val livePages: LiveData>, + val isExpanded: MutableLiveData = MutableLiveData(true), + val notificationCount: MutableLiveData = MutableLiveData(RoomAggregateNotificationCount(0, 0)), + val notifyOfLocalEcho: Boolean = false +) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt new file mode 100644 index 0000000000..f9c5766821 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.DrawableCompat +import androidx.recyclerview.widget.RecyclerView +import im.vector.app.R +import im.vector.app.core.utils.DebouncedClickListener +import im.vector.app.databinding.ItemRoomCategoryBinding +import im.vector.app.features.themes.ThemeUtils + +class SectionHeaderAdapter constructor( + private val onClickAction: (() -> Unit) +) : RecyclerView.Adapter() { + + data class RoomsSectionData( + val name: String, + val isExpanded: Boolean = true, + val notificationCount: Int = 0, + val isHighlighted: Boolean = false, + val isHidden: Boolean = true + ) + + lateinit var roomsSectionData: RoomsSectionData + private set + + fun updateSection(newRoomsSectionData: RoomsSectionData) { + if (!::roomsSectionData.isInitialized || newRoomsSectionData != roomsSectionData) { + roomsSectionData = newRoomsSectionData + notifyDataSetChanged() + } + } + + init { + setHasStableIds(true) + } + + override fun getItemId(position: Int) = roomsSectionData.hashCode().toLong() + + override fun getItemViewType(position: Int) = R.layout.item_room_category + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { + return VH.create(parent, this.onClickAction) + } + + override fun onBindViewHolder(holder: VH, position: Int) { + holder.bind(roomsSectionData) + } + + override fun getItemCount(): Int = if (roomsSectionData.isHidden) 0 else 1 + + class VH constructor( + private val binding: ItemRoomCategoryBinding, + onClickAction: (() -> Unit) + ) : RecyclerView.ViewHolder(binding.root) { + + init { + binding.root.setOnClickListener(DebouncedClickListener({ + onClickAction.invoke() + })) + } + + fun bind(roomsSectionData: RoomsSectionData) { + binding.roomCategoryTitleView.text = roomsSectionData.name + val tintColor = ThemeUtils.getColor(binding.root.context, R.attr.riotx_text_secondary) + val expandedArrowDrawableRes = if (roomsSectionData.isExpanded) R.drawable.ic_expand_more_white else R.drawable.ic_expand_less_white + val expandedArrowDrawable = ContextCompat.getDrawable(binding.root.context, expandedArrowDrawableRes)?.also { + DrawableCompat.setTint(it, tintColor) + } + binding.roomCategoryUnreadCounterBadgeView.render(UnreadCounterBadgeView.State(roomsSectionData.notificationCount, roomsSectionData.isHighlighted)) + binding.roomCategoryTitleView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null) + } + + companion object { + fun create(parent: ViewGroup, onClickAction: () -> Unit): VH { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_room_category, parent, false) + val binding = ItemRoomCategoryBinding.bind(view) + return VH(binding, onClickAction) + } + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt index a4f617bf5b..494c30aab9 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt @@ -26,9 +26,11 @@ import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.isEdition import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.api.session.room.sender.SenderInfo import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getEditedEventId import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult @@ -42,9 +44,10 @@ import javax.inject.Inject * The NotifiableEventResolver is the only aware of session/store, the NotificationDrawerManager has no knowledge of that, * this pattern allow decoupling between the object responsible of displaying notifications and the matrix sdk. */ -class NotifiableEventResolver @Inject constructor(private val stringProvider: StringProvider, - private val noticeEventFormatter: NoticeEventFormatter, - private val displayableEventFormatter: DisplayableEventFormatter) { +class NotifiableEventResolver @Inject constructor( + private val stringProvider: StringProvider, + private val noticeEventFormatter: NoticeEventFormatter, + private val displayableEventFormatter: DisplayableEventFormatter) { // private val eventDisplay = RiotEventDisplay(context) @@ -84,6 +87,47 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St } } + fun resolveInMemoryEvent(session: Session, event: Event): NotifiableEvent? { + if (event.getClearType() != EventType.MESSAGE) return null + + // Ignore message edition + if (event.isEdition()) return null + + val actions = session.getActions(event) + val notificationAction = actions.toNotificationAction() + + return if (notificationAction.shouldNotify) { + val user = session.getUser(event.senderId!!) ?: return null + + val timelineEvent = TimelineEvent( + root = event, + localId = -1, + eventId = event.eventId!!, + displayIndex = 0, + senderInfo = SenderInfo( + userId = user.userId, + displayName = user.getBestName(), + isUniqueDisplayName = true, + avatarUrl = user.avatarUrl + ) + ) + + val notifiableEvent = resolveMessageEvent(timelineEvent, session) + + if (notifiableEvent == null) { + Timber.d("## Failed to resolve event") + // TODO + null + } else { + notifiableEvent.noisy = !notificationAction.soundName.isNullOrBlank() + notifiableEvent + } + } else { + Timber.d("Matched push rule is set to not notify") + null + } + } + private fun resolveMessageEvent(event: TimelineEvent, session: Session): NotifiableEvent? { // The event only contains an eventId, and roomId (type is m.room.*) , we need to get the displayable content (names, avatar, text, etc...) val room = session.getRoom(event.root.roomId!! /*roomID cannot be null*/) diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index 7f3c0a5beb..7ac9b28b9a 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -26,6 +26,7 @@ import im.vector.app.ActiveSessionDataSource import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.FirstThrottler import im.vector.app.features.settings.VectorPreferences import me.gujun.android.span.span import org.matrix.android.sdk.api.session.Session @@ -88,7 +89,9 @@ class NotificationDrawerManager @Inject constructor(private val context: Context // If we support multi session, event list should be per userId // Currently only manage single session if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) { - Timber.v("%%%%%%%% onNotifiableEventReceived $notifiableEvent") + Timber.d("onNotifiableEventReceived(): $notifiableEvent") + } else { + Timber.d("onNotifiableEventReceived(): is push: ${notifiableEvent.isPushGatewayEvent}") } synchronized(eventList) { val existing = eventList.firstOrNull { it.eventId == notifiableEvent.eventId } @@ -194,10 +197,14 @@ class NotificationDrawerManager @Inject constructor(private val context: Context notificationUtils.cancelNotificationMessage(roomId, ROOM_INVITATION_NOTIFICATION_ID) } + private var firstThrottler = FirstThrottler(200) + fun refreshNotificationDrawer() { // Implement last throttler - Timber.v("refreshNotificationDrawer()") + val canHandle = firstThrottler.canHandle() + Timber.v("refreshNotificationDrawer(), delay: ${canHandle.waitMillis()} ms") backgroundHandler.removeCallbacksAndMessages(null) + backgroundHandler.postDelayed( { try { @@ -206,7 +213,8 @@ class NotificationDrawerManager @Inject constructor(private val context: Context // It can happen if for instance session has been destroyed. It's a bit ugly to try catch like this, but it's safer Timber.w(throwable, "refreshNotificationDrawerBg failure") } - }, 200) + }, + canHandle.waitMillis()) } @WorkerThread @@ -544,7 +552,7 @@ class NotificationDrawerManager @Inject constructor(private val context: Context return bitmapLoader.getRoomBitmap(roomAvatarPath) } - private fun shouldIgnoreMessageEventInRoom(roomId: String?): Boolean { + fun shouldIgnoreMessageEventInRoom(roomId: String?): Boolean { return currentRoomId != null && roomId == currentRoomId } diff --git a/vector/src/main/java/im/vector/app/features/room/VectorRoomDisplayNameFallbackProvider.kt b/vector/src/main/java/im/vector/app/features/room/VectorRoomDisplayNameFallbackProvider.kt index 92408d59f4..33e63434ce 100644 --- a/vector/src/main/java/im/vector/app/features/room/VectorRoomDisplayNameFallbackProvider.kt +++ b/vector/src/main/java/im/vector/app/features/room/VectorRoomDisplayNameFallbackProvider.kt @@ -28,23 +28,36 @@ class VectorRoomDisplayNameFallbackProvider( return context.getString(R.string.room_displayname_room_invite) } - override fun getNameForEmptyRoom(): String { - return context.getString(R.string.room_displayname_empty_room) + override fun getNameForEmptyRoom(isDirect: Boolean, leftMemberNames: List): String { + return if (leftMemberNames.isEmpty()) { + context.getString(R.string.room_displayname_empty_room) + } else { + val was = when (val size = leftMemberNames.size) { + 1 -> getNameFor1member(leftMemberNames[0]) + 2 -> getNameFor2members(leftMemberNames[0], leftMemberNames[1]) + 3 -> getNameFor3members(leftMemberNames[0], leftMemberNames[1], leftMemberNames[2]) + 4 -> getNameFor4members(leftMemberNames[0], leftMemberNames[1], leftMemberNames[2], leftMemberNames[3]) + else -> getNameFor4membersAndMore(leftMemberNames[0], leftMemberNames[1], leftMemberNames[2], size - 3) + } + context.getString(R.string.room_displayname_empty_room_was, was) + } } - override fun getNameFor2members(name1: String?, name2: String?): String { + override fun getNameFor1member(name: String) = name + + override fun getNameFor2members(name1: String, name2: String): String { return context.getString(R.string.room_displayname_two_members, name1, name2) } - override fun getNameFor3members(name1: String?, name2: String?, name3: String?): String { + override fun getNameFor3members(name1: String, name2: String, name3: String): String { return context.getString(R.string.room_displayname_3_members, name1, name2, name3) } - override fun getNameFor4members(name1: String?, name2: String?, name3: String?, name4: String?): String { + override fun getNameFor4members(name1: String, name2: String, name3: String, name4: String): String { return context.getString(R.string.room_displayname_4_members, name1, name2, name3, name4) } - override fun getNameFor4membersAndMore(name1: String?, name2: String?, name3: String?, remainingCount: Int): String { + override fun getNameFor4membersAndMore(name1: String, name2: String, name3: String, remainingCount: Int): String { return context.resources.getQuantityString( R.plurals.room_displayname_four_and_more_members, remainingCount, diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/ExplicitTermFilter.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/ExplicitTermFilter.kt new file mode 100644 index 0000000000..0d1f55485c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/ExplicitTermFilter.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomdirectory + +import im.vector.app.core.utils.AssetReader +import javax.inject.Inject + +class ExplicitTermFilter @Inject constructor( + assetReader: AssetReader +) { + // List of forbidden terms is in file asset forbidden_terms.txt, in lower case + private val explicitTerms = assetReader.readAssetFile("forbidden_terms.txt") + .orEmpty() + .split("\n") + .map { it.trim() } + .distinct() + .filter { it.isNotEmpty() } + + private val explicitContentRegex = explicitTerms + .joinToString(prefix = ".*\\b(", separator = "|", postfix = ")\\b.*") + .toRegex(RegexOption.IGNORE_CASE) + + fun canSearchFor(term: String): Boolean { + return term !in explicitTerms && term != "18+" + } + + fun isValid(str: String): Boolean { + return explicitContentRegex.matches(str.replace("\n", " ")).not() + // Special treatment for "18+" since word boundaries does not work here + && str.contains("18+").not() + } +} diff --git a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt index 5012243e96..7b55e38764 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/RoomDirectoryViewModel.kt @@ -41,12 +41,12 @@ import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryDat import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.rx.rx import timber.log.Timber -import java.util.Locale class RoomDirectoryViewModel @AssistedInject constructor( @Assisted initialState: PublicRoomsViewState, vectorPreferences: VectorPreferences, - private val session: Session + private val session: Session, + private val explicitTermFilter: ExplicitTermFilter ) : VectorViewModel(initialState) { @AssistedFactory @@ -57,11 +57,6 @@ class RoomDirectoryViewModel @AssistedInject constructor( companion object : MvRxViewModelFactory { private const val PUBLIC_ROOMS_LIMIT = 20 - // List of forbidden terms, in lower case - private val explicitContentTerms = listOf( - "nsfw" - ) - @JvmStatic override fun create(viewModelContext: ViewModelContext, state: PublicRoomsViewState): RoomDirectoryViewModel? { val activity: RoomDirectoryActivity = (viewModelContext as ActivityViewModelContext).activity() @@ -165,6 +160,17 @@ class RoomDirectoryViewModel @AssistedInject constructor( } private fun load(filter: String, roomDirectoryData: RoomDirectoryData) { + if (!showAllRooms && !explicitTermFilter.canSearchFor(filter)) { + setState { + copy( + asyncPublicRoomsRequest = Success(Unit), + publicRooms = emptyList(), + hasMore = false + ) + } + return + } + currentJob = viewModelScope.launch { val data = try { session.getPublicRooms(roomDirectoryData.homeServer, @@ -201,11 +207,7 @@ class RoomDirectoryViewModel @AssistedInject constructor( // Filter val newPublicRooms = data.chunk.orEmpty() .filter { - showAllRooms - || "${it.name.orEmpty()} ${it.topic.orEmpty()} ${it.canonicalAlias.orEmpty()}".toLowerCase(Locale.ROOT) - .let { str -> - explicitContentTerms.all { term -> term !in str } - } + showAllRooms || explicitTermFilter.isValid("${it.name.orEmpty()} ${it.topic.orEmpty()}") } setState { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt index bae4847f7e..1d6b056816 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/uploads/RoomUploadsViewModel.kt @@ -127,25 +127,27 @@ class RoomUploadsViewModel @AssistedInject constructor( private fun handleShare(action: RoomUploadsAction.Share) { viewModelScope.launch { - try { + val event = try { val file = session.fileService().downloadFile( messageContent = action.uploadEvent.contentWithAttachmentContent) - _viewEvents.post(RoomUploadsViewEvents.FileReadyForSharing(file)) + RoomUploadsViewEvents.FileReadyForSharing(file) } catch (failure: Throwable) { - _viewEvents.post(RoomUploadsViewEvents.Failure(failure)) + RoomUploadsViewEvents.Failure(failure) } + _viewEvents.post(event) } } private fun handleDownload(action: RoomUploadsAction.Download) { viewModelScope.launch { - try { + val event = try { val file = session.fileService().downloadFile( messageContent = action.uploadEvent.contentWithAttachmentContent) - _viewEvents.post(RoomUploadsViewEvents.FileReadyForSaving(file, action.uploadEvent.contentWithAttachmentContent.body)) + RoomUploadsViewEvents.FileReadyForSaving(file, action.uploadEvent.contentWithAttachmentContent.body) } catch (failure: Throwable) { - _viewEvents.post(RoomUploadsViewEvents.Failure(failure)) + RoomUploadsViewEvents.Failure(failure) } + _viewEvents.post(event) } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt index c9160b8ebc..03b7c16274 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt @@ -20,6 +20,7 @@ import androidx.preference.Preference import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.preference.VectorPreference +import im.vector.app.core.utils.FirstThrottler import im.vector.app.core.utils.copyToClipboard import im.vector.app.core.utils.displayInWebView import im.vector.app.core.utils.openAppSettingsPage @@ -36,6 +37,8 @@ class VectorSettingsHelpAboutFragment @Inject constructor( override var titleRes = R.string.preference_root_help_about override val preferenceXmlRes = R.xml.vector_settings_help_about + private val firstThrottler = FirstThrottler(1000) + override fun bindPref() { // preference to start the App info screen, to facilitate App permissions access findPreference(APP_INFO_LINK_PREFERENCE_KEY)!! @@ -98,7 +101,9 @@ class VectorSettingsHelpAboutFragment @Inject constructor( // third party notice findPreference(VectorPreferences.SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY)!! .onPreferenceClickListener = Preference.OnPreferenceClickListener { - activity?.displayInWebView(VectorSettingsUrls.THIRD_PARTY_LICENSES) + if (firstThrottler.canHandle() is FirstThrottler.CanHandlerResult.Yes) { + activity?.displayInWebView(VectorSettingsUrls.THIRD_PARTY_LICENSES) + } false } diff --git a/vector/src/main/res/raw/emoji_picker_datasource.json b/vector/src/main/res/raw/emoji_picker_datasource.json index cc676a4dd0..6aa3799cf0 100644 --- a/vector/src/main/res/raw/emoji_picker_datasource.json +++ b/vector/src/main/res/raw/emoji_picker_datasource.json @@ -1 +1 @@ -{"compressed":true,"categories":[{"id":"smileys_&_emotion","name":"Smileys & Emotion","emojis":["grinning-face","grinning-face-with-big-eyes","grinning-face-with-smiling-eyes","beaming-face-with-smiling-eyes","grinning-squinting-face","grinning-face-with-sweat","rolling-on-the-floor-laughing","face-with-tears-of-joy","slightly-smiling-face","upsidedown-face","winking-face","smiling-face-with-smiling-eyes","smiling-face-with-halo","smiling-face-with-hearts","smiling-face-with-hearteyes","starstruck","face-blowing-a-kiss","kissing-face","smiling-face","kissing-face-with-closed-eyes","kissing-face-with-smiling-eyes","smiling-face-with-tear","face-savoring-food","face-with-tongue","winking-face-with-tongue","zany-face","squinting-face-with-tongue","moneymouth-face","hugging-face","face-with-hand-over-mouth","shushing-face","thinking-face","zippermouth-face","face-with-raised-eyebrow","neutral-face","expressionless-face","face-without-mouth","face-in-clouds","smirking-face","unamused-face","face-with-rolling-eyes","grimacing-face","face-exhaling","lying-face","relieved-face","pensive-face","sleepy-face","drooling-face","sleeping-face","face-with-medical-mask","face-with-thermometer","face-with-headbandage","nauseated-face","face-vomiting","sneezing-face","hot-face","cold-face","woozy-face","knockedout-face","face-with-spiral-eyes","exploding-head","cowboy-hat-face","partying-face","disguised-face","smiling-face-with-sunglasses","nerd-face","face-with-monocle","confused-face","worried-face","slightly-frowning-face","frowning-face","face-with-open-mouth","hushed-face","astonished-face","flushed-face","pleading-face","frowning-face-with-open-mouth","anguished-face","fearful-face","anxious-face-with-sweat","sad-but-relieved-face","crying-face","loudly-crying-face","face-screaming-in-fear","confounded-face","persevering-face","disappointed-face","downcast-face-with-sweat","weary-face","tired-face","yawning-face","face-with-steam-from-nose","pouting-face","angry-face","face-with-symbols-on-mouth","smiling-face-with-horns","angry-face-with-horns","skull","skull-and-crossbones","pile-of-poo","clown-face","ogre","goblin","ghost","alien","alien-monster","robot","grinning-cat","grinning-cat-with-smiling-eyes","cat-with-tears-of-joy","smiling-cat-with-hearteyes","cat-with-wry-smile","kissing-cat","weary-cat","crying-cat","pouting-cat","seenoevil-monkey","hearnoevil-monkey","speaknoevil-monkey","kiss-mark","love-letter","heart-with-arrow","heart-with-ribbon","sparkling-heart","growing-heart","beating-heart","revolving-hearts","two-hearts","heart-decoration","heart-exclamation","broken-heart","heart-on-fire","mending-heart","red-heart","orange-heart","yellow-heart","green-heart","blue-heart","purple-heart","brown-heart","black-heart","white-heart","hundred-points","anger-symbol","collision","dizzy","sweat-droplets","dashing-away","hole","bomb","speech-balloon","eye-in-speech-bubble","left-speech-bubble","right-anger-bubble","thought-balloon","zzz"]},{"id":"people_&_body","name":"People & Body","emojis":["waving-hand","raised-back-of-hand","hand-with-fingers-splayed","raised-hand","vulcan-salute","ok-hand","pinched-fingers","pinching-hand","victory-hand","crossed-fingers","loveyou-gesture","sign-of-the-horns","call-me-hand","backhand-index-pointing-left","backhand-index-pointing-right","backhand-index-pointing-up","middle-finger","backhand-index-pointing-down","index-pointing-up","thumbs-up","thumbs-down","raised-fist","oncoming-fist","leftfacing-fist","rightfacing-fist","clapping-hands","raising-hands","open-hands","palms-up-together","handshake","folded-hands","writing-hand","nail-polish","selfie","flexed-biceps","mechanical-arm","mechanical-leg","leg","foot","ear","ear-with-hearing-aid","nose","brain","anatomical-heart","lungs","tooth","bone","eyes","eye","tongue","mouth","baby","child","boy","girl","person","person-blond-hair","man","person-beard","man-beard","woman-beard","man-red-hair","man-curly-hair","man-white-hair","man-bald","woman","woman-red-hair","person-red-hair","woman-curly-hair","person-curly-hair","woman-white-hair","person-white-hair","woman-bald","person-bald","woman-blond-hair","man-blond-hair","older-person","old-man","old-woman","person-frowning","man-frowning","woman-frowning","person-pouting","man-pouting","woman-pouting","person-gesturing-no","man-gesturing-no","woman-gesturing-no","person-gesturing-ok","man-gesturing-ok","woman-gesturing-ok","person-tipping-hand","man-tipping-hand","woman-tipping-hand","person-raising-hand","man-raising-hand","woman-raising-hand","deaf-person","deaf-man","deaf-woman","person-bowing","man-bowing","woman-bowing","person-facepalming","man-facepalming","woman-facepalming","person-shrugging","man-shrugging","woman-shrugging","health-worker","man-health-worker","woman-health-worker","student","man-student","woman-student","teacher","man-teacher","woman-teacher","judge","man-judge","woman-judge","farmer","man-farmer","woman-farmer","cook","man-cook","woman-cook","mechanic","man-mechanic","woman-mechanic","factory-worker","man-factory-worker","woman-factory-worker","office-worker","man-office-worker","woman-office-worker","scientist","man-scientist","woman-scientist","technologist","man-technologist","woman-technologist","singer","man-singer","woman-singer","artist","man-artist","woman-artist","pilot","man-pilot","woman-pilot","astronaut","man-astronaut","woman-astronaut","firefighter","man-firefighter","woman-firefighter","police-officer","man-police-officer","woman-police-officer","detective","man-detective","woman-detective","guard","man-guard","woman-guard","ninja","construction-worker","man-construction-worker","woman-construction-worker","prince","princess","person-wearing-turban","man-wearing-turban","woman-wearing-turban","person-with-skullcap","woman-with-headscarf","person-in-tuxedo","man-in-tuxedo","woman-in-tuxedo","person-with-veil","man-with-veil","woman-with-veil","pregnant-woman","breastfeeding","woman-feeding-baby","man-feeding-baby","person-feeding-baby","baby-angel","santa-claus","mrs-claus","mx-claus","superhero","man-superhero","woman-superhero","supervillain","man-supervillain","woman-supervillain","mage","man-mage","woman-mage","fairy","man-fairy","woman-fairy","vampire","man-vampire","woman-vampire","merperson","merman","mermaid","elf","man-elf","woman-elf","genie","man-genie","woman-genie","zombie","man-zombie","woman-zombie","person-getting-massage","man-getting-massage","woman-getting-massage","person-getting-haircut","man-getting-haircut","woman-getting-haircut","person-walking","man-walking","woman-walking","person-standing","man-standing","woman-standing","person-kneeling","man-kneeling","woman-kneeling","person-with-white-cane","man-with-white-cane","woman-with-white-cane","person-in-motorized-wheelchair","man-in-motorized-wheelchair","woman-in-motorized-wheelchair","person-in-manual-wheelchair","man-in-manual-wheelchair","woman-in-manual-wheelchair","person-running","man-running","woman-running","woman-dancing","man-dancing","person-in-suit-levitating","people-with-bunny-ears","men-with-bunny-ears","women-with-bunny-ears","person-in-steamy-room","man-in-steamy-room","woman-in-steamy-room","person-climbing","man-climbing","woman-climbing","person-fencing","horse-racing","skier","snowboarder","person-golfing","man-golfing","woman-golfing","person-surfing","man-surfing","woman-surfing","person-rowing-boat","man-rowing-boat","woman-rowing-boat","person-swimming","man-swimming","woman-swimming","person-bouncing-ball","man-bouncing-ball","woman-bouncing-ball","person-lifting-weights","man-lifting-weights","woman-lifting-weights","person-biking","man-biking","woman-biking","person-mountain-biking","man-mountain-biking","woman-mountain-biking","person-cartwheeling","man-cartwheeling","woman-cartwheeling","people-wrestling","men-wrestling","women-wrestling","person-playing-water-polo","man-playing-water-polo","woman-playing-water-polo","person-playing-handball","man-playing-handball","woman-playing-handball","person-juggling","man-juggling","woman-juggling","person-in-lotus-position","man-in-lotus-position","woman-in-lotus-position","person-taking-bath","person-in-bed","people-holding-hands","women-holding-hands","woman-and-man-holding-hands","men-holding-hands","kiss","kiss-woman-man","kiss-man-man","kiss-woman-woman","couple-with-heart","couple-with-heart-woman-man","couple-with-heart-man-man","couple-with-heart-woman-woman","family","family-man-woman-boy","family-man-woman-girl","family-man-woman-girl-boy","family-man-woman-boy-boy","family-man-woman-girl-girl","family-man-man-boy","family-man-man-girl","family-man-man-girl-boy","family-man-man-boy-boy","family-man-man-girl-girl","family-woman-woman-boy","family-woman-woman-girl","family-woman-woman-girl-boy","family-woman-woman-boy-boy","family-woman-woman-girl-girl","family-man-boy","family-man-boy-boy","family-man-girl","family-man-girl-boy","family-man-girl-girl","family-woman-boy","family-woman-boy-boy","family-woman-girl","family-woman-girl-boy","family-woman-girl-girl","speaking-head","bust-in-silhouette","busts-in-silhouette","people-hugging","footprints"]},{"id":"animals_&_nature","name":"Animals & Nature","emojis":["monkey-face","monkey","gorilla","orangutan","dog-face","dog","guide-dog","service-dog","poodle","wolf","fox","raccoon","cat-face","cat","black-cat","lion","tiger-face","tiger","leopard","horse-face","horse","unicorn","zebra","deer","bison","cow-face","ox","water-buffalo","cow","pig-face","pig","boar","pig-nose","ram","ewe","goat","camel","twohump-camel","llama","giraffe","elephant","mammoth","rhinoceros","hippopotamus","mouse-face","mouse","rat","hamster","rabbit-face","rabbit","chipmunk","beaver","hedgehog","bat","bear","polar-bear","koala","panda","sloth","otter","skunk","kangaroo","badger","paw-prints","turkey","chicken","rooster","hatching-chick","baby-chick","frontfacing-baby-chick","bird","penguin","dove","eagle","duck","swan","owl","dodo","feather","flamingo","peacock","parrot","frog","crocodile","turtle","lizard","snake","dragon-face","dragon","sauropod","trex","spouting-whale","whale","dolphin","seal","fish","tropical-fish","blowfish","shark","octopus","spiral-shell","snail","butterfly","bug","ant","honeybee","beetle","lady-beetle","cricket","cockroach","spider","spider-web","scorpion","mosquito","fly","worm","microbe","bouquet","cherry-blossom","white-flower","rosette","rose","wilted-flower","hibiscus","sunflower","blossom","tulip","seedling","potted-plant","evergreen-tree","deciduous-tree","palm-tree","cactus","sheaf-of-rice","herb","shamrock","four-leaf-clover","maple-leaf","fallen-leaf","leaf-fluttering-in-wind"]},{"id":"food_&_drink","name":"Food & Drink","emojis":["grapes","melon","watermelon","tangerine","lemon","banana","pineapple","mango","red-apple","green-apple","pear","peach","cherries","strawberry","blueberries","kiwi-fruit","tomato","olive","coconut","avocado","eggplant","potato","carrot","ear-of-corn","hot-pepper","bell-pepper","cucumber","leafy-green","broccoli","garlic","onion","mushroom","peanuts","chestnut","bread","croissant","baguette-bread","flatbread","pretzel","bagel","pancakes","waffle","cheese-wedge","meat-on-bone","poultry-leg","cut-of-meat","bacon","hamburger","french-fries","pizza","hot-dog","sandwich","taco","burrito","tamale","stuffed-flatbread","falafel","egg","cooking","shallow-pan-of-food","pot-of-food","fondue","bowl-with-spoon","green-salad","popcorn","butter","salt","canned-food","bento-box","rice-cracker","rice-ball","cooked-rice","curry-rice","steaming-bowl","spaghetti","roasted-sweet-potato","oden","sushi","fried-shrimp","fish-cake-with-swirl","moon-cake","dango","dumpling","fortune-cookie","takeout-box","crab","lobster","shrimp","squid","oyster","soft-ice-cream","shaved-ice","ice-cream","doughnut","cookie","birthday-cake","shortcake","cupcake","pie","chocolate-bar","candy","lollipop","custard","honey-pot","baby-bottle","glass-of-milk","hot-beverage","teapot","teacup-without-handle","sake","bottle-with-popping-cork","wine-glass","cocktail-glass","tropical-drink","beer-mug","clinking-beer-mugs","clinking-glasses","tumbler-glass","cup-with-straw","bubble-tea","beverage-box","mate","ice","chopsticks","fork-and-knife-with-plate","fork-and-knife","spoon","kitchen-knife","amphora"]},{"id":"travel_&_places","name":"Travel & Places","emojis":["globe-showing-europeafrica","globe-showing-americas","globe-showing-asiaaustralia","globe-with-meridians","world-map","map-of-japan","compass","snowcapped-mountain","mountain","volcano","mount-fuji","camping","beach-with-umbrella","desert","desert-island","national-park","stadium","classical-building","building-construction","brick","rock","wood","hut","houses","derelict-house","house","house-with-garden","office-building","japanese-post-office","post-office","hospital","bank","hotel","love-hotel","convenience-store","school","department-store","factory","japanese-castle","castle","wedding","tokyo-tower","statue-of-liberty","church","mosque","hindu-temple","synagogue","shinto-shrine","kaaba","fountain","tent","foggy","night-with-stars","cityscape","sunrise-over-mountains","sunrise","cityscape-at-dusk","sunset","bridge-at-night","hot-springs","carousel-horse","ferris-wheel","roller-coaster","barber-pole","circus-tent","locomotive","railway-car","highspeed-train","bullet-train","train","metro","light-rail","station","tram","monorail","mountain-railway","tram-car","bus","oncoming-bus","trolleybus","minibus","ambulance","fire-engine","police-car","oncoming-police-car","taxi","oncoming-taxi","automobile","oncoming-automobile","sport-utility-vehicle","pickup-truck","delivery-truck","articulated-lorry","tractor","racing-car","motorcycle","motor-scooter","manual-wheelchair","motorized-wheelchair","auto-rickshaw","bicycle","kick-scooter","skateboard","roller-skate","bus-stop","motorway","railway-track","oil-drum","fuel-pump","police-car-light","horizontal-traffic-light","vertical-traffic-light","stop-sign","construction","anchor","sailboat","canoe","speedboat","passenger-ship","ferry","motor-boat","ship","airplane","small-airplane","airplane-departure","airplane-arrival","parachute","seat","helicopter","suspension-railway","mountain-cableway","aerial-tramway","satellite","rocket","flying-saucer","bellhop-bell","luggage","hourglass-done","hourglass-not-done","watch","alarm-clock","stopwatch","timer-clock","mantelpiece-clock","twelve-oclock","twelvethirty","one-oclock","onethirty","two-oclock","twothirty","three-oclock","threethirty","four-oclock","fourthirty","five-oclock","fivethirty","six-oclock","sixthirty","seven-oclock","seventhirty","eight-oclock","eightthirty","nine-oclock","ninethirty","ten-oclock","tenthirty","eleven-oclock","eleventhirty","new-moon","waxing-crescent-moon","first-quarter-moon","waxing-gibbous-moon","full-moon","waning-gibbous-moon","last-quarter-moon","waning-crescent-moon","crescent-moon","new-moon-face","first-quarter-moon-face","last-quarter-moon-face","thermometer","sun","full-moon-face","sun-with-face","ringed-planet","star","glowing-star","shooting-star","milky-way","cloud","sun-behind-cloud","cloud-with-lightning-and-rain","sun-behind-small-cloud","sun-behind-large-cloud","sun-behind-rain-cloud","cloud-with-rain","cloud-with-snow","cloud-with-lightning","tornado","fog","wind-face","cyclone","rainbow","closed-umbrella","umbrella","umbrella-with-rain-drops","umbrella-on-ground","high-voltage","snowflake","snowman","snowman-without-snow","comet","fire","droplet","water-wave"]},{"id":"activities","name":"Activities","emojis":["jackolantern","christmas-tree","fireworks","sparkler","firecracker","sparkles","balloon","party-popper","confetti-ball","tanabata-tree","pine-decoration","japanese-dolls","carp-streamer","wind-chime","moon-viewing-ceremony","red-envelope","ribbon","wrapped-gift","reminder-ribbon","admission-tickets","ticket","military-medal","trophy","sports-medal","1st-place-medal","2nd-place-medal","3rd-place-medal","soccer-ball","baseball","softball","basketball","volleyball","american-football","rugby-football","tennis","flying-disc","bowling","cricket-game","field-hockey","ice-hockey","lacrosse","ping-pong","badminton","boxing-glove","martial-arts-uniform","goal-net","flag-in-hole","ice-skate","fishing-pole","diving-mask","running-shirt","skis","sled","curling-stone","bullseye","yoyo","kite","pool-8-ball","crystal-ball","magic-wand","nazar-amulet","video-game","joystick","slot-machine","game-die","puzzle-piece","teddy-bear","piata","nesting-dolls","spade-suit","heart-suit","diamond-suit","club-suit","chess-pawn","joker","mahjong-red-dragon","flower-playing-cards","performing-arts","framed-picture","artist-palette","thread","sewing-needle","yarn","knot"]},{"id":"objects","name":"Objects","emojis":["glasses","sunglasses","goggles","lab-coat","safety-vest","necktie","tshirt","jeans","scarf","gloves","coat","socks","dress","kimono","sari","onepiece-swimsuit","briefs","shorts","bikini","womans-clothes","purse","handbag","clutch-bag","shopping-bags","backpack","thong-sandal","mans-shoe","running-shoe","hiking-boot","flat-shoe","highheeled-shoe","womans-sandal","ballet-shoes","womans-boot","crown","womans-hat","top-hat","graduation-cap","billed-cap","military-helmet","rescue-workers-helmet","prayer-beads","lipstick","ring","gem-stone","muted-speaker","speaker-low-volume","speaker-medium-volume","speaker-high-volume","loudspeaker","megaphone","postal-horn","bell","bell-with-slash","musical-score","musical-note","musical-notes","studio-microphone","level-slider","control-knobs","microphone","headphone","radio","saxophone","accordion","guitar","musical-keyboard","trumpet","violin","banjo","drum","long-drum","mobile-phone","mobile-phone-with-arrow","telephone","telephone-receiver","pager","fax-machine","battery","electric-plug","laptop","desktop-computer","printer","keyboard","computer-mouse","trackball","computer-disk","floppy-disk","optical-disk","dvd","abacus","movie-camera","film-frames","film-projector","clapper-board","television","camera","camera-with-flash","video-camera","videocassette","magnifying-glass-tilted-left","magnifying-glass-tilted-right","candle","light-bulb","flashlight","red-paper-lantern","diya-lamp","notebook-with-decorative-cover","closed-book","open-book","green-book","blue-book","orange-book","books","notebook","ledger","page-with-curl","scroll","page-facing-up","newspaper","rolledup-newspaper","bookmark-tabs","bookmark","label","money-bag","coin","yen-banknote","dollar-banknote","euro-banknote","pound-banknote","money-with-wings","credit-card","receipt","chart-increasing-with-yen","envelope","email","incoming-envelope","envelope-with-arrow","outbox-tray","inbox-tray","package","closed-mailbox-with-raised-flag","closed-mailbox-with-lowered-flag","open-mailbox-with-raised-flag","open-mailbox-with-lowered-flag","postbox","ballot-box-with-ballot","pencil","black-nib","fountain-pen","pen","paintbrush","crayon","memo","briefcase","file-folder","open-file-folder","card-index-dividers","calendar","tearoff-calendar","spiral-notepad","spiral-calendar","card-index","chart-increasing","chart-decreasing","bar-chart","clipboard","pushpin","round-pushpin","paperclip","linked-paperclips","straight-ruler","triangular-ruler","scissors","card-file-box","file-cabinet","wastebasket","locked","unlocked","locked-with-pen","locked-with-key","key","old-key","hammer","axe","pick","hammer-and-pick","hammer-and-wrench","dagger","crossed-swords","water-pistol","boomerang","bow-and-arrow","shield","carpentry-saw","wrench","screwdriver","nut-and-bolt","gear","clamp","balance-scale","white-cane","link","chains","hook","toolbox","magnet","ladder","alembic","test-tube","petri-dish","dna","microscope","telescope","satellite-antenna","syringe","drop-of-blood","pill","adhesive-bandage","stethoscope","door","elevator","mirror","window","bed","couch-and-lamp","chair","toilet","plunger","shower","bathtub","mouse-trap","razor","lotion-bottle","safety-pin","broom","basket","roll-of-paper","bucket","soap","toothbrush","sponge","fire-extinguisher","shopping-cart","cigarette","coffin","headstone","funeral-urn","moai","placard"]},{"id":"symbols","name":"Symbols","emojis":["atm-sign","litter-in-bin-sign","potable-water","wheelchair-symbol","mens-room","womens-room","restroom","baby-symbol","water-closet","passport-control","customs","baggage-claim","left-luggage","warning","children-crossing","no-entry","prohibited","no-bicycles","no-smoking","no-littering","nonpotable-water","no-pedestrians","no-mobile-phones","no-one-under-eighteen","radioactive","biohazard","up-arrow","upright-arrow","right-arrow","downright-arrow","down-arrow","downleft-arrow","left-arrow","upleft-arrow","updown-arrow","leftright-arrow","right-arrow-curving-left","left-arrow-curving-right","right-arrow-curving-up","right-arrow-curving-down","clockwise-vertical-arrows","counterclockwise-arrows-button","back-arrow","end-arrow","on-arrow","soon-arrow","top-arrow","place-of-worship","atom-symbol","om","star-of-david","wheel-of-dharma","yin-yang","latin-cross","orthodox-cross","star-and-crescent","peace-symbol","menorah","dotted-sixpointed-star","aries","taurus","gemini","cancer","leo","virgo","libra","scorpio","sagittarius","capricorn","aquarius","pisces","ophiuchus","shuffle-tracks-button","repeat-button","repeat-single-button","play-button","fastforward-button","next-track-button","play-or-pause-button","reverse-button","fast-reverse-button","last-track-button","upwards-button","fast-up-button","downwards-button","fast-down-button","pause-button","stop-button","record-button","eject-button","cinema","dim-button","bright-button","antenna-bars","vibration-mode","mobile-phone-off","female-sign","male-sign","transgender-symbol","multiply","plus","minus","divide","infinity","double-exclamation-mark","exclamation-question-mark","red-question-mark","white-question-mark","white-exclamation-mark","red-exclamation-mark","wavy-dash","currency-exchange","heavy-dollar-sign","medical-symbol","recycling-symbol","fleurdelis","trident-emblem","name-badge","japanese-symbol-for-beginner","hollow-red-circle","check-mark-button","check-box-with-check","check-mark","cross-mark","cross-mark-button","curly-loop","double-curly-loop","part-alternation-mark","eightspoked-asterisk","eightpointed-star","sparkle","copyright","registered","trade-mark","keycap","keycap","keycap-0","keycap-1","keycap-2","keycap-3","keycap-4","keycap-5","keycap-6","keycap-7","keycap-8","keycap-9","keycap-10","input-latin-uppercase","input-latin-lowercase","input-numbers","input-symbols","input-latin-letters","a-button-blood-type","ab-button-blood-type","b-button-blood-type","cl-button","cool-button","free-button","information","id-button","circled-m","new-button","ng-button","o-button-blood-type","ok-button","p-button","sos-button","up-button","vs-button","japanese-here-button","japanese-service-charge-button","japanese-monthly-amount-button","japanese-not-free-of-charge-button","japanese-reserved-button","japanese-bargain-button","japanese-discount-button","japanese-free-of-charge-button","japanese-prohibited-button","japanese-acceptable-button","japanese-application-button","japanese-passing-grade-button","japanese-vacancy-button","japanese-congratulations-button","japanese-secret-button","japanese-open-for-business-button","japanese-no-vacancy-button","red-circle","orange-circle","yellow-circle","green-circle","blue-circle","purple-circle","brown-circle","black-circle","white-circle","red-square","orange-square","yellow-square","green-square","blue-square","purple-square","brown-square","black-large-square","white-large-square","black-medium-square","white-medium-square","black-mediumsmall-square","white-mediumsmall-square","black-small-square","white-small-square","large-orange-diamond","large-blue-diamond","small-orange-diamond","small-blue-diamond","red-triangle-pointed-up","red-triangle-pointed-down","diamond-with-a-dot","radio-button","white-square-button","black-square-button"]},{"id":"flags","name":"Flags","emojis":["chequered-flag","triangular-flag","crossed-flags","black-flag","white-flag","rainbow-flag","transgender-flag","pirate-flag","flag-ascension-island","flag-andorra","flag-united-arab-emirates","flag-afghanistan","flag-antigua--barbuda","flag-anguilla","flag-albania","flag-armenia","flag-angola","flag-antarctica","flag-argentina","flag-american-samoa","flag-austria","flag-australia","flag-aruba","flag-land-islands","flag-azerbaijan","flag-bosnia--herzegovina","flag-barbados","flag-bangladesh","flag-belgium","flag-burkina-faso","flag-bulgaria","flag-bahrain","flag-burundi","flag-benin","flag-st-barthlemy","flag-bermuda","flag-brunei","flag-bolivia","flag-caribbean-netherlands","flag-brazil","flag-bahamas","flag-bhutan","flag-bouvet-island","flag-botswana","flag-belarus","flag-belize","flag-canada","flag-cocos-keeling-islands","flag-congo--kinshasa","flag-central-african-republic","flag-congo--brazzaville","flag-switzerland","flag-cte-divoire","flag-cook-islands","flag-chile","flag-cameroon","flag-china","flag-colombia","flag-clipperton-island","flag-costa-rica","flag-cuba","flag-cape-verde","flag-curaao","flag-christmas-island","flag-cyprus","flag-czechia","flag-germany","flag-diego-garcia","flag-djibouti","flag-denmark","flag-dominica","flag-dominican-republic","flag-algeria","flag-ceuta--melilla","flag-ecuador","flag-estonia","flag-egypt","flag-western-sahara","flag-eritrea","flag-spain","flag-ethiopia","flag-european-union","flag-finland","flag-fiji","flag-falkland-islands","flag-micronesia","flag-faroe-islands","flag-france","flag-gabon","flag-united-kingdom","flag-grenada","flag-georgia","flag-french-guiana","flag-guernsey","flag-ghana","flag-gibraltar","flag-greenland","flag-gambia","flag-guinea","flag-guadeloupe","flag-equatorial-guinea","flag-greece","flag-south-georgia--south-sandwich-islands","flag-guatemala","flag-guam","flag-guineabissau","flag-guyana","flag-hong-kong-sar-china","flag-heard--mcdonald-islands","flag-honduras","flag-croatia","flag-haiti","flag-hungary","flag-canary-islands","flag-indonesia","flag-ireland","flag-israel","flag-isle-of-man","flag-india","flag-british-indian-ocean-territory","flag-iraq","flag-iran","flag-iceland","flag-italy","flag-jersey","flag-jamaica","flag-jordan","flag-japan","flag-kenya","flag-kyrgyzstan","flag-cambodia","flag-kiribati","flag-comoros","flag-st-kitts--nevis","flag-north-korea","flag-south-korea","flag-kuwait","flag-cayman-islands","flag-kazakhstan","flag-laos","flag-lebanon","flag-st-lucia","flag-liechtenstein","flag-sri-lanka","flag-liberia","flag-lesotho","flag-lithuania","flag-luxembourg","flag-latvia","flag-libya","flag-morocco","flag-monaco","flag-moldova","flag-montenegro","flag-st-martin","flag-madagascar","flag-marshall-islands","flag-north-macedonia","flag-mali","flag-myanmar-burma","flag-mongolia","flag-macao-sar-china","flag-northern-mariana-islands","flag-martinique","flag-mauritania","flag-montserrat","flag-malta","flag-mauritius","flag-maldives","flag-malawi","flag-mexico","flag-malaysia","flag-mozambique","flag-namibia","flag-new-caledonia","flag-niger","flag-norfolk-island","flag-nigeria","flag-nicaragua","flag-netherlands","flag-norway","flag-nepal","flag-nauru","flag-niue","flag-new-zealand","flag-oman","flag-panama","flag-peru","flag-french-polynesia","flag-papua-new-guinea","flag-philippines","flag-pakistan","flag-poland","flag-st-pierre--miquelon","flag-pitcairn-islands","flag-puerto-rico","flag-palestinian-territories","flag-portugal","flag-palau","flag-paraguay","flag-qatar","flag-runion","flag-romania","flag-serbia","flag-russia","flag-rwanda","flag-saudi-arabia","flag-solomon-islands","flag-seychelles","flag-sudan","flag-sweden","flag-singapore","flag-st-helena","flag-slovenia","flag-svalbard--jan-mayen","flag-slovakia","flag-sierra-leone","flag-san-marino","flag-senegal","flag-somalia","flag-suriname","flag-south-sudan","flag-so-tom--prncipe","flag-el-salvador","flag-sint-maarten","flag-syria","flag-eswatini","flag-tristan-da-cunha","flag-turks--caicos-islands","flag-chad","flag-french-southern-territories","flag-togo","flag-thailand","flag-tajikistan","flag-tokelau","flag-timorleste","flag-turkmenistan","flag-tunisia","flag-tonga","flag-turkey","flag-trinidad--tobago","flag-tuvalu","flag-taiwan","flag-tanzania","flag-ukraine","flag-uganda","flag-us-outlying-islands","flag-united-nations","flag-united-states","flag-uruguay","flag-uzbekistan","flag-vatican-city","flag-st-vincent--grenadines","flag-venezuela","flag-british-virgin-islands","flag-us-virgin-islands","flag-vietnam","flag-vanuatu","flag-wallis--futuna","flag-samoa","flag-kosovo","flag-yemen","flag-mayotte","flag-south-africa","flag-zambia","flag-zimbabwe","flag-england","flag-scotland","flag-wales"]}],"emojis":{"grinning-face":{"a":"Grinning Face","b":"1F600","j":["grin","joy",":D","smile","happy","face"]},"grinning-face-with-big-eyes":{"a":"Grinning Face with Big Eyes","b":"1F603","j":["mouth","open","joy",":)",":D","funny","smile","happy","face","haha"]},"grinning-face-with-smiling-eyes":{"a":"Grinning Face with Smiling Eyes","b":"1F604","j":["mouth","open","eye","joy",":)","like",":D","funny","smile","happy","laugh","face","haha"]},"beaming-face-with-smiling-eyes":{"a":"Beaming Face with Smiling Eyes","b":"1F601","j":["grin","joy","eye","smile","kawaii","happy","face"]},"grinning-squinting-face":{"a":"Grinning Squinting Face","b":"1F606","j":["mouth","lol","joy","glad","XD","haha","smile","happy","laugh","face","satisfied"]},"grinning-face-with-sweat":{"a":"Grinning Face with Sweat","b":"1F605","j":["relief","open","sweat","hot","smile","cold","happy","laugh","face"]},"rolling-on-the-floor-laughing":{"a":"Rolling on the Floor Laughing","b":"1F923","j":["floor","lol","rolling","rotfl","rofl","laughing","laugh","face","haha"]},"face-with-tears-of-joy":{"a":"Face with Tears of Joy","b":"1F602","j":["tears","weep","tear","cry","joy","happytears","happy","laugh","face","haha"]},"slightly-smiling-face":{"a":"Slightly Smiling Face","b":"1F642","j":["smile","face"]},"upsidedown-face":{"a":"Upside-Down Face","b":"1F643","j":["upside_down_face","flipped","upside-down","smile","face","silly"]},"winking-face":{"a":"Winking Face","b":"1F609","j":["mischievous",";)","wink","eye","smile","secret","happy","face"]},"smiling-face-with-smiling-eyes":{"a":"Smiling Face with Smiling Eyes","b":"1F60A","j":["blush","eye","joy","smile","embarrassed","shy","crush","happy","flushed","face"]},"smiling-face-with-halo":{"a":"Smiling Face with Halo","b":"1F607","j":["halo","fantasy","innocent","angel","face","heaven"]},"smiling-face-with-hearts":{"a":"Smiling Face with Hearts","b":"1F970","j":["infatuation","hearts","love","like","valentines","affection","in love","crush","adore","face"]},"smiling-face-with-hearteyes":{"a":"Smiling Face with Heart-Eyes","b":"1F60D","j":["heart","smiling face with heart-eyes","eye","love","infatuation","like","affection","smile","valentines","smiling_face_with_heart_eyes","crush","face"]},"starstruck":{"a":"Star-Struck","b":"1F929","j":["starry-eyed","eyes","star_struck","grinning","smile","star-struck","starry","face","star"]},"face-blowing-a-kiss":{"a":"Face Blowing a Kiss","b":"1F618","j":["infatuation","love","kiss","like","valentines","affection","face"]},"kissing-face":{"a":"Kissing Face","b":"1F617","j":["3","infatuation","kiss","love","like","valentines","face"]},"smiling-face":{"a":"Smiling Face","b":"263A","j":["massage","outlined","blush","smile","happiness","relaxed","face"]},"kissing-face-with-closed-eyes":{"a":"Kissing Face with Closed Eyes","b":"1F61A","j":["closed","infatuation","eye","kiss","love","like","valentines","affection","face"]},"kissing-face-with-smiling-eyes":{"a":"Kissing Face with Smiling Eyes","b":"1F619","j":["infatuation","eye","kiss","valentines","smile","affection","face"]},"smiling-face-with-tear":{"a":"Smiling Face with Tear","b":"1F972","j":["touched","sad","grateful","tear","relieved","cry","smiling","proud","pretend"]},"face-savoring-food":{"a":"Face Savoring Food","b":"1F60B","j":["joy","nom","tongue","savouring","smile","delicious","yummy","yum","happy","face","silly"]},"face-with-tongue":{"a":"Face with Tongue","b":"1F61B","j":["prank","mischievous","playful","childish","tongue","smile","face"]},"winking-face-with-tongue":{"a":"Winking Face with Tongue","b":"1F61C","j":["prank","mischievous","playful","childish","wink","eye","tongue","smile","joke","face"]},"zany-face":{"a":"Zany Face","b":"1F92A","j":["eye","small","large","crazy","goofy","face"]},"squinting-face-with-tongue":{"a":"Squinting Face with Tongue","b":"1F61D","j":["prank","mischievous","taste","playful","eye","tongue","horrible","smile","face"]},"moneymouth-face":{"a":"Money-Mouth Face","b":"1F911","j":["mouth","dollar","money-mouth face","money","rich","face","money_mouth_face"]},"hugging-face":{"a":"Hugging Face","b":"1F917","j":["hugging","smile","hug","face"]},"face-with-hand-over-mouth":{"a":"Face with Hand over Mouth","b":"1F92D","j":["surprise","whoops","shock","face","sudden realization"]},"shushing-face":{"a":"Shushing Face","b":"1F92B","j":["shhh","shush","quiet","face"]},"thinking-face":{"a":"Thinking Face","b":"1F914","j":["consider","face","thinking","think","hmmm"]},"zippermouth-face":{"a":"Zipper-Mouth Face","b":"1F910","j":["zipper-mouth face","mouth","sealed","zipper_mouth_face","zipper","secret","face"]},"face-with-raised-eyebrow":{"a":"Face with Raised Eyebrow","b":"1F928","j":["disbelief","surprise","mild surprise","scepticism","disapproval","distrust","face","skeptic"]},"neutral-face":{"a":"Neutral Face","b":"1F610","j":["deadpan",":|","neutral","meh","face","indifference"]},"expressionless-face":{"a":"Expressionless Face","b":"1F611","j":["unexpressive","expressionless","-_-","deadpan","indifferent","meh","face","inexpressive"]},"face-without-mouth":{"a":"Face Without Mouth","b":"1F636","j":["mouth","silent","hellokitty","quiet","face"]},"face-in-clouds":{"a":"⊛ Face in Clouds","b":"1F636-200D-1F32B-FE0F","j":["absentminded","face in clouds","face in the fog","head in clouds"]},"smirking-face":{"a":"Smirking Face","b":"1F60F","j":["prank","smug","mean","sarcasm","smirk","smile","face"]},"unamused-face":{"a":"Unamused Face","b":"1F612","j":["unhappy","straight face","bored","side_eye","skeptical","dubious","sarcasm","unamused","serious","unimpressed","face","indifference"]},"face-with-rolling-eyes":{"a":"Face with Rolling Eyes","b":"1F644","j":["eyeroll","frustrated","rolling","eyes","face"]},"grimacing-face":{"a":"Grimacing Face","b":"1F62C","j":["teeth","grimace","face"]},"face-exhaling":{"a":"⊛ Face Exhaling","b":"1F62E-200D-1F4A8","j":["exhale","face exhaling","gasp","groan","relief","whisper","whistle"]},"lying-face":{"a":"Lying Face","b":"1F925","j":["lie","face","pinocchio"]},"relieved-face":{"a":"Relieved Face","b":"1F60C","j":["massage","relieved","phew","happiness","relaxed","face"]},"pensive-face":{"a":"Pensive Face","b":"1F614","j":["dejected","sad","pensive","depressed","upset","face"]},"sleepy-face":{"a":"Sleepy Face","b":"1F62A","j":["tired","rest","nap","face","sleep"]},"drooling-face":{"a":"Drooling Face","b":"1F924","j":["face","drooling"]},"sleeping-face":{"a":"Sleeping Face","b":"1F634","j":["night","sleepy","zzz","tired","face","sleep"]},"face-with-medical-mask":{"a":"Face with Medical Mask","b":"1F637","j":["sick","doctor","ill","disease","mask","cold","face"]},"face-with-thermometer":{"a":"Face with Thermometer","b":"1F912","j":["sick","thermometer","temperature","fever","ill","cold","face"]},"face-with-headbandage":{"a":"Face with Head-Bandage","b":"1F915","j":["face_with_head_bandage","injured","injury","clumsy","face with head-bandage","bandage","face","hurt"]},"nauseated-face":{"a":"Nauseated Face","b":"1F922","j":["sick","throw up","nauseated","ill","green","vomit","gross","face"]},"face-vomiting":{"a":"Face Vomiting","b":"1F92E","j":["puke","vomit","face","sick"]},"sneezing-face":{"a":"Sneezing Face","b":"1F927","j":["sick","allergy","sneeze","gesundheit","face"]},"hot-face":{"a":"Hot Face","b":"1F975","j":["red","hot","heat","feverish","heat stroke","sweating","face","red-faced"]},"cold-face":{"a":"Cold Face","b":"1F976","j":["icicles","freezing","frozen","blue-faced","frostbite","cold","face","blue"]},"woozy-face":{"a":"Woozy Face","b":"1F974","j":["dizzy","face","wavy mouth","tipsy","wavy","uneven eyes","intoxicated"]},"knockedout-face":{"a":"Knocked-out Face","b":"1F635","j":["knocked-out face","dizzy_face","dizzy","knocked out","xox","spent","unconscious","face","dead"]},"face-with-spiral-eyes":{"a":"⊛ Face with Spiral Eyes","b":"1F635-200D-1F4AB","j":["dizzy","face with spiral eyes","hypnotized","spiral","trouble","whoa"]},"exploding-head":{"a":"Exploding Head","b":"1F92F","j":["shocked","mind","blown","mind blown","face"]},"cowboy-hat-face":{"a":"Cowboy Hat Face","b":"1F920","j":["cowboy","cowgirl","face","hat"]},"partying-face":{"a":"Partying Face","b":"1F973","j":["hat","celebration","horn","woohoo","face","party"]},"disguised-face":{"a":"Disguised Face","b":"1F978","j":["brows","glasses","incognito","moustache","disguise","nose","face","pretent"]},"smiling-face-with-sunglasses":{"a":"Smiling Face with Sunglasses","b":"1F60E","j":["cool","sunglass","sun","smile","bright","beach","summer","sunglasses","face"]},"nerd-face":{"a":"Nerd Face","b":"1F913","j":["nerdy","dork","nerd","geek","face"]},"face-with-monocle":{"a":"Face with Monocle","b":"1F9D0","j":["wealthy","stuffy","face"]},"confused-face":{"a":"Confused Face","b":"1F615","j":[":/","face","meh","confused","huh","hmmm","weird","indifference"]},"worried-face":{"a":"Worried Face","b":"1F61F","j":[":(","worried","nervous","concern","face"]},"slightly-frowning-face":{"a":"Slightly Frowning Face","b":"1F641","j":["sad","frowning","frown","disappointed","upset","face"]},"frowning-face":{"a":"Frowning Face","b":"2639","j":["sad","upset","face","frown"]},"face-with-open-mouth":{"a":"Face with Open Mouth","b":"1F62E","j":["surprise","mouth","wow","whoa","open","sympathy","impressed",":O","face"]},"hushed-face":{"a":"Hushed Face","b":"1F62F","j":["shh","woo","surprised","hushed","stunned","face"]},"astonished-face":{"a":"Astonished Face","b":"1F632","j":["shocked","poisoned","totally","surprised","astonished","xox","face"]},"flushed-face":{"a":"Flushed Face","b":"1F633","j":["blush","flattered","shy","dazed","flushed","face"]},"pleading-face":{"a":"Pleading Face","b":"1F97A","j":["begging","mercy","face","puppy eyes"]},"frowning-face-with-open-mouth":{"a":"Frowning Face with Open Mouth","b":"1F626","j":["mouth","aw","what","open","frown","face"]},"anguished-face":{"a":"Anguished Face","b":"1F627","j":["stunned","anguished","face","nervous"]},"fearful-face":{"a":"Fearful Face","b":"1F628","j":["fearful","terrified","oops","scared","fear","nervous","huh","face"]},"anxious-face-with-sweat":{"a":"Anxious Face with Sweat","b":"1F630","j":["sweat","nervous","cold","rushed","face","blue"]},"sad-but-relieved-face":{"a":"Sad but Relieved Face","b":"1F625","j":["whew","relieved","sweat","phew","disappointed","nervous","face"]},"crying-face":{"a":"Crying Face","b":"1F622","j":["sad","tears","tear",":'(","cry","depressed","upset","face"]},"loudly-crying-face":{"a":"Loudly Crying Face","b":"1F62D","j":["sad","tears","tear","cry","depressed","sob","upset","face"]},"face-screaming-in-fear":{"a":"Face Screaming in Fear","b":"1F631","j":["munch","scared","scream","omg","fear","face"]},"confounded-face":{"a":"Confounded Face","b":"1F616","j":["unwell","sick","confounded","oops",":S","confused","face"]},"persevering-face":{"a":"Persevering Face","b":"1F623","j":["sick","no","oops","upset","face","persevere"]},"disappointed-face":{"a":"Disappointed Face","b":"1F61E","j":["sad","depressed","disappointed",":(","upset","face"]},"downcast-face-with-sweat":{"a":"Downcast Face with Sweat","b":"1F613","j":["sad","exercise","sweat","hot","tired","cold","face"]},"weary-face":{"a":"Weary Face","b":"1F629","j":["sad","weary","sleepy","frustrated","tired","upset","face"]},"tired-face":{"a":"Tired Face","b":"1F62B","j":["sick","frustrated","tired","upset","whine","face"]},"yawning-face":{"a":"Yawning Face","b":"1F971","j":["","bored","yawn","tired","sleepy"]},"face-with-steam-from-nose":{"a":"Face with Steam From Nose","b":"1F624","j":["gas","won","pride","phew","proud","face","triumph"]},"pouting-face":{"a":"Pouting Face","b":"1F621","j":["despise","red","mad","hate","angry","pouting","face","rage"]},"angry-face":{"a":"Angry Face","b":"1F620","j":["annoyed","mad","frustrated","angry","anger","face"]},"face-with-symbols-on-mouth":{"a":"Face with Symbols on Mouth","b":"1F92C","j":["swearing","profanity","cursing","expletive","cussing","face"]},"smiling-face-with-horns":{"a":"Smiling Face with Horns","b":"1F608","j":["devil","horns","fairy tale","fantasy","smile","face"]},"angry-face-with-horns":{"a":"Angry Face with Horns","b":"1F47F","j":["demon","imp","devil","horns","fantasy","angry","face"]},"skull":{"a":"Skull","b":"1F480","j":["monster","skeleton","fairy tale","creepy","death","face","dead"]},"skull-and-crossbones":{"a":"Skull and Crossbones","b":"2620","j":["monster","scary","poison","skull","crossbones","deadly","evil","danger","death","face","pirate"]},"pile-of-poo":{"a":"Pile of Poo","b":"1F4A9","j":["monster","poop","dung","shitface","hankey","poo","shit","turd","face","fail"]},"clown-face":{"a":"Clown Face","b":"1F921","j":["face","clown"]},"ogre":{"a":"Ogre","b":"1F479","j":["monster","demon","devil","red","fairy tale","fantasy","troll","halloween","creepy","creature","mask","japanese","scary","face"]},"goblin":{"a":"Goblin","b":"1F47A","j":["monster","red","fairy tale","fantasy","creature","creepy","mask","japanese","scary","evil","face"]},"ghost":{"a":"Ghost","b":"1F47B","j":["monster","fairy tale","fantasy","halloween","spooky","creature","scary","face"]},"alien":{"a":"Alien","b":"1F47D","j":["paul","ufo","outer_space","UFO","extraterrestrial","fantasy","creature","face","weird"]},"alien-monster":{"a":"Alien Monster","b":"1F47E","j":["monster","ufo","arcade","alien","extraterrestrial","creature","game","face","play"]},"robot":{"a":"Robot","b":"1F916","j":["monster","machine","bot","computer","face"]},"grinning-cat":{"a":"Grinning Cat","b":"1F63A","j":["mouth","open","animal","grinning","smile","cats","happy","cat","face"]},"grinning-cat-with-smiling-eyes":{"a":"Grinning Cat with Smiling Eyes","b":"1F638","j":["grin","animal","eye","cats","smile","cat","face"]},"cat-with-tears-of-joy":{"a":"Cat with Tears of Joy","b":"1F639","j":["tears","tear","joy","animal","cats","happy","cat","face","haha"]},"smiling-cat-with-hearteyes":{"a":"Smiling Cat with Heart-Eyes","b":"1F63B","j":["heart","smiling cat with heart-eyes","smiling_cat_with_heart_eyes","eye","love","animal","like","affection","smile","cats","valentines","cat","face"]},"cat-with-wry-smile":{"a":"Cat with Wry Smile","b":"1F63C","j":["animal","ironic","smirk","smile","cats","wry","cat","face"]},"kissing-cat":{"a":"Kissing Cat","b":"1F63D","j":["animal","kiss","eye","cats","cat","face"]},"weary-cat":{"a":"Weary Cat","b":"1F640","j":["weary","cat","animal","munch","scared","surprised","scream","cats","oh","face"]},"crying-cat":{"a":"Crying Cat","b":"1F63F","j":["sad","tears","tear","weep","cry","animal","cats","upset","cat","face"]},"pouting-cat":{"a":"Pouting Cat","b":"1F63E","j":["animal","cats","pouting","cat","face"]},"seenoevil-monkey":{"a":"See-No-Evil Monkey","b":"1F648","j":["animal","see","see-no-evil monkey","face","see_no_evil_monkey","nature","forbidden","evil","monkey","haha"]},"hearnoevil-monkey":{"a":"Hear-No-Evil Monkey","b":"1F649","j":["hear-no-evil monkey","hear","monkey","animal","hear_no_evil_monkey","nature","forbidden","evil","face"]},"speaknoevil-monkey":{"a":"Speak-No-Evil Monkey","b":"1F64A","j":["speak","speak-no-evil monkey","animal","face","omg","speak_no_evil_monkey","nature","forbidden","evil","monkey"]},"kiss-mark":{"a":"Kiss Mark","b":"1F48B","j":["lips","love","kiss","like","valentines","affection","face"]},"love-letter":{"a":"Love Letter","b":"1F48C","j":["heart","letter","envelope","email","love","like","valentines","mail","affection"]},"heart-with-arrow":{"a":"Heart with Arrow","b":"1F498","j":["cupid","heart","arrow","love","like","valentines","affection"]},"heart-with-ribbon":{"a":"Heart with Ribbon","b":"1F49D","j":["ribbon","love","valentines","valentine"]},"sparkling-heart":{"a":"Sparkling Heart","b":"1F496","j":["sparkle","love","like","valentines","excited","affection"]},"growing-heart":{"a":"Growing Heart","b":"1F497","j":["pink","pulse","growing","love","like","valentines","nervous","excited","affection"]},"beating-heart":{"a":"Beating Heart","b":"1F493","j":["heartbeat","pink","heart","love","like","valentines","beating","affection","pulsating"]},"revolving-hearts":{"a":"Revolving Hearts","b":"1F49E","j":["love","like","valentines","revolving","affection"]},"two-hearts":{"a":"Two Hearts","b":"1F495","j":["heart","love","like","valentines","affection"]},"heart-decoration":{"a":"Heart Decoration","b":"1F49F","j":["purple-square","heart","love","like"]},"heart-exclamation":{"a":"Heart Exclamation","b":"2763","j":["exclamation","decoration","punctuation","love","mark"]},"broken-heart":{"a":"Broken Heart","b":"1F494","j":["sad","heart","heartbreak","break","sorry","broken"]},"heart-on-fire":{"a":"⊛ Heart on Fire","b":"2764-FE0F-200D-1F525","j":["burn","heart","heart on fire","love","lust","sacred heart"]},"mending-heart":{"a":"⊛ Mending Heart","b":"2764-FE0F-200D-1FA79","j":["healthier","improving","mending","mending heart","recovering","recuperating","well"]},"red-heart":{"a":"Red Heart","b":"2764","j":["heart","love","valentines","like"]},"orange-heart":{"a":"Orange Heart","b":"1F9E1","j":["love","like","orange","valentines","affection"]},"yellow-heart":{"a":"Yellow Heart","b":"1F49B","j":["yellow","love","like","valentines","affection"]},"green-heart":{"a":"Green Heart","b":"1F49A","j":["love","like","green","valentines","affection"]},"blue-heart":{"a":"Blue Heart","b":"1F499","j":["love","like","valentines","affection","blue"]},"purple-heart":{"a":"Purple Heart","b":"1F49C","j":["love","like","purple","valentines","affection"]},"brown-heart":{"a":"Brown Heart","b":"1F90E","j":["brown","coffee","heart"]},"black-heart":{"a":"Black Heart","b":"1F5A4","j":["black","wicked","evil"]},"white-heart":{"a":"White Heart","b":"1F90D","j":["heart","pure","white"]},"hundred-points":{"a":"Hundred Points","b":"1F4AF","j":["test","100","exam","score","numbers","full","quiz","hundred","century","pass","perfect"]},"anger-symbol":{"a":"Anger Symbol","b":"1F4A2","j":["mad","comic","angry"]},"collision":{"a":"Collision","b":"1F4A5","j":["explode","boom","bomb","comic","blown","explosion"]},"dizzy":{"a":"Dizzy","b":"1F4AB","j":["sparkle","magic","shoot","comic","star"]},"sweat-droplets":{"a":"Sweat Droplets","b":"1F4A6","j":["sweat","oops","water","drip","comic","splashing"]},"dashing-away":{"a":"Dashing Away","b":"1F4A8","j":["running","air","fart","shoo","wind","smoke","comic","fast","puff","dash"]},"hole":{"a":"Hole","b":"1F573","j":["embarrassing"]},"bomb":{"a":"Bomb","b":"1F4A3","j":["explode","terrorism","boom","comic","explosion"]},"speech-balloon":{"a":"Speech Balloon","b":"1F4AC","j":["chatting","dialog","speech","bubble","words","message","balloon","comic","talk"]},"eye-in-speech-bubble":{"a":"Eye in Speech Bubble","b":"1F441-FE0F-200D-1F5E8-FE0F","j":["speech bubble","info","eye","witness"]},"left-speech-bubble":{"a":"Left Speech Bubble","b":"1F5E8","j":["chatting","dialog","speech","words","message","talk"]},"right-anger-bubble":{"a":"Right Anger Bubble","b":"1F5EF","j":["mad","speech","bubble","thinking","balloon","angry","caption"]},"thought-balloon":{"a":"Thought Balloon","b":"1F4AD","j":["thought","dream","cloud","bubble","speech","thinking","balloon","comic"]},"zzz":{"a":"Zzz","b":"1F4A4","j":["dream","tired","comic","sleepy","sleep"]},"waving-hand":{"a":"Waving Hand","b":"1F44B","j":["palm","gesture","farewell","wave","hands","hello","hi","solong","waving","hand","goodbye"]},"raised-back-of-hand":{"a":"Raised Back of Hand","b":"1F91A","j":["raised","fingers","backhand"]},"hand-with-fingers-splayed":{"a":"Hand with Fingers Splayed","b":"1F590","j":["splayed","palm","finger","hand","fingers"]},"raised-hand":{"a":"Raised Hand","b":"270B","j":["stop","palm","high 5","highfive","high five","hand","fingers","ban"]},"vulcan-salute":{"a":"Vulcan Salute","b":"1F596","j":["spock","finger","hand","star trek","fingers","vulcan"]},"ok-hand":{"a":"Ok Hand","b":"1F44C","j":["ok","perfect","limbs","hand","OK","okay","fingers"]},"pinched-fingers":{"a":"Pinched Fingers","b":"1F90C","j":["pinched","small","interrogation","hand gesture","sarcastic","size","fingers","tiny"]},"pinching-hand":{"a":"Pinching Hand","b":"1F90F","j":["small","size","small amount","tiny"]},"victory-hand":{"a":"Victory Hand","b":"270C","j":["victory","ohyeah","peace","two","v","hand","fingers"]},"crossed-fingers":{"a":"Crossed Fingers","b":"1F91E","j":["lucky","cross","luck","finger","good","hand"]},"loveyou-gesture":{"a":"Love-You Gesture","b":"1F91F","j":["gesture","love-you gesture","hand","ILY","fingers","love_you_gesture"]},"sign-of-the-horns":{"a":"Sign of the Horns","b":"1F918","j":["evil_eye","sign_of_horns","horns","finger","rock_on","hand","fingers","rock-on"]},"call-me-hand":{"a":"Call Me Hand","b":"1F919","j":["hand","gesture","call","hands"]},"backhand-index-pointing-left":{"a":"Backhand Index Pointing Left","b":"1F448","j":["point","backhand","index","left","direction","finger","hand","fingers"]},"backhand-index-pointing-right":{"a":"Backhand Index Pointing Right","b":"1F449","j":["point","backhand","index","direction","right","finger","hand","fingers"]},"backhand-index-pointing-up":{"a":"Backhand Index Pointing Up","b":"1F446","j":["up","point","backhand","direction","finger","hand","fingers"]},"middle-finger":{"a":"Middle Finger","b":"1F595","j":["flipping","rude","middle","finger","hand","fingers"]},"backhand-index-pointing-down":{"a":"Backhand Index Pointing Down","b":"1F447","j":["point","backhand","direction","down","finger","hand","fingers"]},"index-pointing-up":{"a":"Index Pointing Up","b":"261D","j":["up","point","index","direction","finger","hand","fingers"]},"thumbs-up":{"a":"Thumbs Up","b":"1F44D","j":["up","cool","thumb","+1","thumbsup","awesome","like","good","accept","hand","yes","agree"]},"thumbs-down":{"a":"Thumbs Down","b":"1F44E","j":["-1","thumb","no","dislike","down","thumbsdown","hand"]},"raised-fist":{"a":"Raised Fist","b":"270A","j":["punch","fist","grasp","clenched","hand","fingers"]},"oncoming-fist":{"a":"Oncoming Fist","b":"1F44A","j":["punch","attack","fist","hit","clenched","angry","hand","violence"]},"leftfacing-fist":{"a":"Left-Facing Fist","b":"1F91B","j":["fist","left-facing fist","leftwards","left_facing_fist","hand","fistbump"]},"rightfacing-fist":{"a":"Right-Facing Fist","b":"1F91C","j":["rightwards","fist","right-facing fist","right_facing_fist","hand","fistbump"]},"clapping-hands":{"a":"Clapping Hands","b":"1F44F","j":["congrats","praise","applause","yay","hands","hand","clap"]},"raising-hands":{"a":"Raising Hands","b":"1F64C","j":["hooray","gesture","celebration","raised","yea","hands","hand"]},"open-hands":{"a":"Open Hands","b":"1F450","j":["open","butterfly","hands","hand","fingers"]},"palms-up-together":{"a":"Palms Up Together","b":"1F932","j":["gesture","hands","cupped hands","cupped","prayer"]},"handshake":{"a":"Handshake","b":"1F91D","j":["agreement","hand","meeting","shake"]},"folded-hands":{"a":"Folded Hands","b":"1F64F","j":["thanks","wish","please","high 5","hope","namaste","pray","highfive","high five","hand","ask"]},"writing-hand":{"a":"Writing Hand","b":"270D","j":["lower_left_ballpoint_pen","write","stationery","compose","hand"]},"nail-polish":{"a":"Nail Polish","b":"1F485","j":["manicure","polish","beauty","finger","fashion","nail","cosmetics","care"]},"selfie":{"a":"Selfie","b":"1F933","j":["camera","phone"]},"flexed-biceps":{"a":"Flexed Biceps","b":"1F4AA","j":["strong","hand","flex","biceps","comic","arm","summer","muscle"]},"mechanical-arm":{"a":"Mechanical Arm","b":"1F9BE","j":["accessibility","prosthetic"]},"mechanical-leg":{"a":"Mechanical Leg","b":"1F9BF","j":["accessibility","prosthetic"]},"leg":{"a":"Leg","b":"1F9B5","j":["limb","kick"]},"foot":{"a":"Foot","b":"1F9B6","j":["stomp","kick"]},"ear":{"a":"Ear","b":"1F442","j":["listen","hear","body","sound","face"]},"ear-with-hearing-aid":{"a":"Ear with Hearing Aid","b":"1F9BB","j":["accessibility","hard of hearing"]},"nose":{"a":"Nose","b":"1F443","j":["body","smell","sniff"]},"brain":{"a":"Brain","b":"1F9E0","j":["smart","intelligent"]},"anatomical-heart":{"a":"Anatomical Heart","b":"1FAC0","j":["heartbeat","heart","pulse","health","organ","anatomical","cardiology"]},"lungs":{"a":"Lungs","b":"1FAC1","j":["organ","respiration","breathe","exhalation","breath","inhalation"]},"tooth":{"a":"Tooth","b":"1F9B7","j":["teeth","dentist"]},"bone":{"a":"Bone","b":"1F9B4","j":["skeleton"]},"eyes":{"a":"Eyes","b":"1F440","j":["stalk","eye","see","peek","watch","look","face"]},"eye":{"a":"Eye","b":"1F441","j":["body","stare","see","watch","look","face"]},"tongue":{"a":"Tongue","b":"1F445","j":["body","mouth","playful"]},"mouth":{"a":"Mouth","b":"1F444","j":["lips","kiss"]},"baby":{"a":"Baby","b":"1F476","j":["young","child","girl","boy","toddler"]},"child":{"a":"Child","b":"1F9D2","j":["gender-neutral","unspecified gender","young"]},"boy":{"a":"Boy","b":"1F466","j":["male","man","teenager","young","guy"]},"girl":{"a":"Girl","b":"1F467","j":["Virgo","woman","teenager","young","female","zodiac"]},"person":{"a":"Person","b":"1F9D1","j":["gender-neutral","unspecified gender","adult"]},"person-blond-hair":{"a":"Person: Blond Hair","b":"1F471","j":["blond","blond-haired person","hair","person: blond hair","hairstyle"]},"man":{"a":"Man","b":"1F468","j":["dad","classy","adult","mustache","guy","moustache","father","sir"]},"person-beard":{"a":"Person: Beard","b":"1F9D4","j":["man_beard","beard","bewhiskered","person: beard","person"]},"man-beard":{"a":"⊛ Man: Beard","b":"1F9D4-200D-2642-FE0F","j":["beard","man","man: beard"]},"woman-beard":{"a":"⊛ Woman: Beard","b":"1F9D4-200D-2640-FE0F","j":["beard","woman","woman: beard"]},"man-red-hair":{"a":"Man: Red Hair","b":"1F468-200D-1F9B0","j":["man","hairstyle","adult","red hair"]},"man-curly-hair":{"a":"Man: Curly Hair","b":"1F468-200D-1F9B1","j":["man","curly hair","hairstyle","adult"]},"man-white-hair":{"a":"Man: White Hair","b":"1F468-200D-1F9B3","j":["man","old","adult","white hair","elder"]},"man-bald":{"a":"Man: Bald","b":"1F468-200D-1F9B2","j":["man","hairless","bald","adult"]},"woman":{"a":"Woman","b":"1F469","j":["female","lady","adult","girls"]},"woman-red-hair":{"a":"Woman: Red Hair","b":"1F469-200D-1F9B0","j":["woman","hairstyle","adult","red hair"]},"person-red-hair":{"a":"Person: Red Hair","b":"1F9D1-200D-1F9B0","j":["gender-neutral","unspecified gender","adult","red hair","hairstyle","person"]},"woman-curly-hair":{"a":"Woman: Curly Hair","b":"1F469-200D-1F9B1","j":["curly hair","hairstyle","woman","adult"]},"person-curly-hair":{"a":"Person: Curly Hair","b":"1F9D1-200D-1F9B1","j":["gender-neutral","unspecified gender","adult","curly hair","hairstyle","person"]},"woman-white-hair":{"a":"Woman: White Hair","b":"1F469-200D-1F9B3","j":["woman","old","adult","white hair","elder"]},"person-white-hair":{"a":"Person: White Hair","b":"1F9D1-200D-1F9B3","j":["gender-neutral","unspecified gender","old","adult","white hair","person","elder"]},"woman-bald":{"a":"Woman: Bald","b":"1F469-200D-1F9B2","j":["woman","bald","hairless","adult"]},"person-bald":{"a":"Person: Bald","b":"1F9D1-200D-1F9B2","j":["gender-neutral","bald","unspecified gender","adult","hairless","person"]},"woman-blond-hair":{"a":"Woman: Blond Hair","b":"1F471-200D-2640-FE0F","j":["blond-haired woman","woman","female","hair","girl","woman: blond hair","person","blonde"]},"man-blond-hair":{"a":"Man: Blond Hair","b":"1F471-200D-2642-FE0F","j":["blond","male","man","blond-haired man","man: blond hair","hair","guy","boy","person","blonde"]},"older-person":{"a":"Older Person","b":"1F9D3","j":["gender-neutral","unspecified gender","old","adult","senior","human","elder"]},"old-man":{"a":"Old Man","b":"1F474","j":["male","men","man","old","adult","senior","human","elder"]},"old-woman":{"a":"Old Woman","b":"1F475","j":["old","woman","adult","female","lady","senior","women","human","elder"]},"person-frowning":{"a":"Person Frowning","b":"1F64D","j":["frown","gesture","worried"]},"man-frowning":{"a":"Man Frowning","b":"1F64D-200D-2642-FE0F","j":["sad","unhappy","frowning","male","man","gesture","depressed","boy","discouraged"]},"woman-frowning":{"a":"Woman Frowning","b":"1F64D-200D-2640-FE0F","j":["sad","unhappy","frowning","gesture","woman","depressed","female","girl","discouraged"]},"person-pouting":{"a":"Person Pouting","b":"1F64E","j":["gesture","upset","pouting"]},"man-pouting":{"a":"Man Pouting","b":"1F64E-200D-2642-FE0F","j":["male","man","gesture","pouting","boy"]},"woman-pouting":{"a":"Woman Pouting","b":"1F64E-200D-2640-FE0F","j":["gesture","woman","female","girl","pouting"]},"person-gesturing-no":{"a":"Person Gesturing No","b":"1F645","j":["prohibited","gesture","hand","forbidden","decline","person gesturing NO"]},"man-gesturing-no":{"a":"Man Gesturing No","b":"1F645-200D-2642-FE0F","j":["male","nope","man","gesture","prohibited","hand","forbidden","boy","man gesturing NO"]},"woman-gesturing-no":{"a":"Woman Gesturing No","b":"1F645-200D-2640-FE0F","j":["nope","prohibited","gesture","woman","woman gesturing NO","female","girl","hand","forbidden"]},"person-gesturing-ok":{"a":"Person Gesturing Ok","b":"1F646","j":["person gesturing OK","gesture","hand","OK","agree"]},"man-gesturing-ok":{"a":"Man Gesturing Ok","b":"1F646-200D-2642-FE0F","j":["male","men","OK","man","gesture","man gesturing OK","hand","boy","human","blue"]},"woman-gesturing-ok":{"a":"Woman Gesturing Ok","b":"1F646-200D-2640-FE0F","j":["pink","OK","gesture","woman","female","woman gesturing OK","girl","hand","women","human"]},"person-tipping-hand":{"a":"Person Tipping Hand","b":"1F481","j":["information","tipping","sassy","help","hand"]},"man-tipping-hand":{"a":"Man Tipping Hand","b":"1F481-200D-2642-FE0F","j":["male","information","man","sassy","tipping hand","boy","human"]},"woman-tipping-hand":{"a":"Woman Tipping Hand","b":"1F481-200D-2640-FE0F","j":["information","woman","sassy","female","girl","tipping hand","human"]},"person-raising-hand":{"a":"Person Raising Hand","b":"1F64B","j":["gesture","raised","question","hand","happy"]},"man-raising-hand":{"a":"Man Raising Hand","b":"1F64B-200D-2642-FE0F","j":["male","man","gesture","raising hand","boy"]},"woman-raising-hand":{"a":"Woman Raising Hand","b":"1F64B-200D-2640-FE0F","j":["gesture","woman","female","raising hand","girl"]},"deaf-person":{"a":"Deaf Person","b":"1F9CF","j":["accessibility","ear","deaf","hear"]},"deaf-man":{"a":"Deaf Man","b":"1F9CF-200D-2642-FE0F","j":["man","accessibility","deaf"]},"deaf-woman":{"a":"Deaf Woman","b":"1F9CF-200D-2640-FE0F","j":["woman","deaf","accessibility"]},"person-bowing":{"a":"Person Bowing","b":"1F647","j":["bow","gesture","sorry","respectiful","apology"]},"man-bowing":{"a":"Man Bowing","b":"1F647-200D-2642-FE0F","j":["favor","male","man","gesture","sorry","apology","boy","bowing"]},"woman-bowing":{"a":"Woman Bowing","b":"1F647-200D-2640-FE0F","j":["favor","gesture","woman","sorry","apology","female","girl","bowing"]},"person-facepalming":{"a":"Person Facepalming","b":"1F926","j":["disbelief","palm","exasperation","disappointed","face"]},"man-facepalming":{"a":"Man Facepalming","b":"1F926-200D-2642-FE0F","j":["disbelief","male","exasperation","man","facepalm","boy"]},"woman-facepalming":{"a":"Woman Facepalming","b":"1F926-200D-2640-FE0F","j":["disbelief","exasperation","woman","facepalm","female","girl"]},"person-shrugging":{"a":"Person Shrugging","b":"1F937","j":["shrug","ignorance","regardless","doubt","indifference"]},"man-shrugging":{"a":"Man Shrugging","b":"1F937-200D-2642-FE0F","j":["male","shrug","ignorance","man","indifferent","confused","boy","doubt","indifference"]},"woman-shrugging":{"a":"Woman Shrugging","b":"1F937-200D-2640-FE0F","j":["shrug","ignorance","woman","indifferent","female","girl","confused","doubt","indifference"]},"health-worker":{"a":"Health Worker","b":"1F9D1-200D-2695-FE0F","j":["nurse","doctor","healthcare","hospital","therapist"]},"man-health-worker":{"a":"Man Health Worker","b":"1F468-200D-2695-FE0F","j":["nurse","man","doctor","healthcare","human","therapist"]},"woman-health-worker":{"a":"Woman Health Worker","b":"1F469-200D-2695-FE0F","j":["nurse","woman","doctor","healthcare","human","therapist"]},"student":{"a":"Student","b":"1F9D1-200D-1F393","j":["graduate","learn"]},"man-student":{"a":"Man Student","b":"1F468-200D-1F393","j":["man","student","human","graduate"]},"woman-student":{"a":"Woman Student","b":"1F469-200D-1F393","j":["woman","student","human","graduate"]},"teacher":{"a":"Teacher","b":"1F9D1-200D-1F3EB","j":["instructor","professor"]},"man-teacher":{"a":"Man Teacher","b":"1F468-200D-1F3EB","j":["man","teacher","professor","instructor","human"]},"woman-teacher":{"a":"Woman Teacher","b":"1F469-200D-1F3EB","j":["woman","teacher","professor","instructor","human"]},"judge":{"a":"Judge","b":"1F9D1-200D-2696-FE0F","j":["law","scales","justice"]},"man-judge":{"a":"Man Judge","b":"1F468-200D-2696-FE0F","j":["man","scales","court","judge","human","justice"]},"woman-judge":{"a":"Woman Judge","b":"1F469-200D-2696-FE0F","j":["woman","scales","court","judge","human","justice"]},"farmer":{"a":"Farmer","b":"1F9D1-200D-1F33E","j":["rancher","crops","gardener"]},"man-farmer":{"a":"Man Farmer","b":"1F468-200D-1F33E","j":["man","rancher","farmer","gardener","human"]},"woman-farmer":{"a":"Woman Farmer","b":"1F469-200D-1F33E","j":["rancher","woman","farmer","gardener","human"]},"cook":{"a":"Cook","b":"1F9D1-200D-1F373","j":["culinary","kitchen","chef","food"]},"man-cook":{"a":"Man Cook","b":"1F468-200D-1F373","j":["cook","human","chef","man"]},"woman-cook":{"a":"Woman Cook","b":"1F469-200D-1F373","j":["cook","woman","human","chef"]},"mechanic":{"a":"Mechanic","b":"1F9D1-200D-1F527","j":["tradesperson","plumber","electrician","worker","technician"]},"man-mechanic":{"a":"Man Mechanic","b":"1F468-200D-1F527","j":["plumber","tradesperson","wrench","man","electrician","mechanic","human"]},"woman-mechanic":{"a":"Woman Mechanic","b":"1F469-200D-1F527","j":["tradesperson","plumber","wrench","woman","electrician","mechanic","human"]},"factory-worker":{"a":"Factory Worker","b":"1F9D1-200D-1F3ED","j":["industrial","assembly","worker","factory","labor"]},"man-factory-worker":{"a":"Man Factory Worker","b":"1F468-200D-1F3ED","j":["man","industrial","assembly","worker","factory","human"]},"woman-factory-worker":{"a":"Woman Factory Worker","b":"1F469-200D-1F3ED","j":["woman","industrial","assembly","worker","factory","human"]},"office-worker":{"a":"Office Worker","b":"1F9D1-200D-1F4BC","j":["business","white-collar","architect","manager"]},"man-office-worker":{"a":"Man Office Worker","b":"1F468-200D-1F4BC","j":["business","architect","man","white-collar","human","manager"]},"woman-office-worker":{"a":"Woman Office Worker","b":"1F469-200D-1F4BC","j":["business","architect","woman","white-collar","human","manager"]},"scientist":{"a":"Scientist","b":"1F9D1-200D-1F52C","j":["chemist","chemistry","biologist","physicist","engineer"]},"man-scientist":{"a":"Man Scientist","b":"1F468-200D-1F52C","j":["man","chemist","biologist","physicist","human","engineer","scientist"]},"woman-scientist":{"a":"Woman Scientist","b":"1F469-200D-1F52C","j":["woman","chemist","biologist","physicist","human","engineer","scientist"]},"technologist":{"a":"Technologist","b":"1F9D1-200D-1F4BB","j":["inventor","software","computer","developer","coder"]},"man-technologist":{"a":"Man Technologist","b":"1F468-200D-1F4BB","j":["inventor","man","engineer","software","laptop","computer","developer","coder","technologist","human","programmer"]},"woman-technologist":{"a":"Woman Technologist","b":"1F469-200D-1F4BB","j":["inventor","woman","engineer","software","laptop","computer","developer","coder","technologist","human","programmer"]},"singer":{"a":"Singer","b":"1F9D1-200D-1F3A4","j":["actor","entertainer","performer","rock","song","artist","star"]},"man-singer":{"a":"Man Singer","b":"1F468-200D-1F3A4","j":["actor","entertainer","singer","rockstar","man","rock","human","star"]},"woman-singer":{"a":"Woman Singer","b":"1F469-200D-1F3A4","j":["actor","entertainer","singer","rockstar","woman","rock","human","star"]},"artist":{"a":"Artist","b":"1F9D1-200D-1F3A8","j":["palette","creativity","draw","painting"]},"man-artist":{"a":"Man Artist","b":"1F468-200D-1F3A8","j":["man","painter","palette","artist","human"]},"woman-artist":{"a":"Woman Artist","b":"1F469-200D-1F3A8","j":["woman","painter","palette","artist","human"]},"pilot":{"a":"Pilot","b":"1F9D1-200D-2708-FE0F","j":["airplane","plane","fly"]},"man-pilot":{"a":"Man Pilot","b":"1F468-200D-2708-FE0F","j":["man","plane","pilot","human","aviator"]},"woman-pilot":{"a":"Woman Pilot","b":"1F469-200D-2708-FE0F","j":["woman","plane","pilot","human","aviator"]},"astronaut":{"a":"Astronaut","b":"1F9D1-200D-1F680","j":["outerspace","rocket"]},"man-astronaut":{"a":"Man Astronaut","b":"1F468-200D-1F680","j":["man","space","astronaut","rocket","human"]},"woman-astronaut":{"a":"Woman Astronaut","b":"1F469-200D-1F680","j":["woman","space","astronaut","rocket","human"]},"firefighter":{"a":"Firefighter","b":"1F9D1-200D-1F692","j":["fire","firetruck"]},"man-firefighter":{"a":"Man Firefighter","b":"1F468-200D-1F692","j":["man","firetruck","firefighter","human","fireman"]},"woman-firefighter":{"a":"Woman Firefighter","b":"1F469-200D-1F692","j":["firetruck","woman","firefighter","human","fireman"]},"police-officer":{"a":"Police Officer","b":"1F46E","j":["cop","officer","police"]},"man-police-officer":{"a":"Man Police Officer","b":"1F46E-200D-2642-FE0F","j":["arrest","cop","man","911","officer","police","enforcement","legal","law"]},"woman-police-officer":{"a":"Woman Police Officer","b":"1F46E-200D-2640-FE0F","j":["arrest","cop","woman","911","female","officer","police","enforcement","legal","law"]},"detective":{"a":"Detective","b":"1F575","j":["spy","sleuth","human"]},"man-detective":{"a":"Man Detective","b":"1F575-FE0F-200D-2642-FE0F","j":["sleuth","man","spy","crime","detective"]},"woman-detective":{"a":"Woman Detective","b":"1F575-FE0F-200D-2640-FE0F","j":["sleuth","spy","woman","detective","female","human"]},"guard":{"a":"Guard","b":"1F482","j":["protect"]},"man-guard":{"a":"Man Guard","b":"1F482-200D-2642-FE0F","j":["uk","male","british","man","royal","guy","gb","guard"]},"woman-guard":{"a":"Woman Guard","b":"1F482-200D-2640-FE0F","j":["uk","british","royal","woman","female","gb","guard"]},"ninja":{"a":"Ninja","b":"1F977","j":["fighter","ninjutsu","stealth","skills","hidden","japanese"]},"construction-worker":{"a":"Construction Worker","b":"1F477","j":["hat","construction","build","worker","labor"]},"man-construction-worker":{"a":"Man Construction Worker","b":"1F477-200D-2642-FE0F","j":["male","wip","man","construction","build","guy","worker","human","labor"]},"woman-construction-worker":{"a":"Woman Construction Worker","b":"1F477-200D-2640-FE0F","j":["wip","woman","construction","build","female","worker","human","labor"]},"prince":{"a":"Prince","b":"1F934","j":["male","man","royal","crown","boy","king"]},"princess":{"a":"Princess","b":"1F478","j":["blond","queen","fairy tale","royal","woman","crown","fantasy","female","girl"]},"person-wearing-turban":{"a":"Person Wearing Turban","b":"1F473","j":["headdress","turban"]},"man-wearing-turban":{"a":"Man Wearing Turban","b":"1F473-200D-2642-FE0F","j":["male","turban","hinduism","man","indian","arabs"]},"woman-wearing-turban":{"a":"Woman Wearing Turban","b":"1F473-200D-2640-FE0F","j":["turban","hinduism","woman","female","indian","arabs"]},"person-with-skullcap":{"a":"Person with Skullcap","b":"1F472","j":["male","hat","cap","man_with_skullcap","skullcap","chinese","gua pi mao","boy","person"]},"woman-with-headscarf":{"a":"Woman with Headscarf","b":"1F9D5","j":["hijab","head kerchief","mantilla","bandana","female","tichel","headscarf"]},"person-in-tuxedo":{"a":"Person in Tuxedo","b":"1F935","j":["wedding","marriage","couple","groom","tuxedo","person","man_in_tuxedo"]},"man-in-tuxedo":{"a":"Man in Tuxedo","b":"1F935-200D-2642-FE0F","j":["man","tuxedo","fashion","formal"]},"woman-in-tuxedo":{"a":"Woman in Tuxedo","b":"1F935-200D-2640-FE0F","j":["woman","tuxedo","fashion","formal"]},"person-with-veil":{"a":"Person with Veil","b":"1F470","j":["bride","wedding","veil","marriage","woman","couple","bride_with_veil","person"]},"man-with-veil":{"a":"Man with Veil","b":"1F470-200D-2642-FE0F","j":["man","marriage","wedding","veil"]},"woman-with-veil":{"a":"Woman with Veil","b":"1F470-200D-2640-FE0F","j":["woman","marriage","wedding","veil"]},"pregnant-woman":{"a":"Pregnant Woman","b":"1F930","j":["woman","pregnant","baby"]},"breastfeeding":{"a":"Breast-Feeding","b":"1F931","j":["breast","breast-feeding","breast_feeding","nursing","baby"]},"woman-feeding-baby":{"a":"Woman Feeding Baby","b":"1F469-200D-1F37C","j":["food","birth","woman","feeding","nursing","baby"]},"man-feeding-baby":{"a":"Man Feeding Baby","b":"1F468-200D-1F37C","j":["food","birth","man","feeding","nursing","baby"]},"person-feeding-baby":{"a":"Person Feeding Baby","b":"1F9D1-200D-1F37C","j":["food","birth","feeding","nursing","baby","person"]},"baby-angel":{"a":"Baby Angel","b":"1F47C","j":["fairy tale","halo","fantasy","wings","baby","angel","face","heaven"]},"santa-claus":{"a":"Santa Claus","b":"1F385","j":["xmas","male","festival","man","father christmas","celebration","santa","Christmas","claus","father"]},"mrs-claus":{"a":"Mrs. Claus","b":"1F936","j":["xmas","mother","Mrs.","woman","celebration","female","mother christmas","Christmas","claus"]},"mx-claus":{"a":"Mx Claus","b":"1F9D1-200D-1F384","j":["Claus, christmas","christmas"]},"superhero":{"a":"Superhero","b":"1F9B8","j":["heroine","marvel","good","superpower","hero"]},"man-superhero":{"a":"Man Superhero","b":"1F9B8-200D-2642-FE0F","j":["male","man","superpowers","good","superpower","hero"]},"woman-superhero":{"a":"Woman Superhero","b":"1F9B8-200D-2640-FE0F","j":["woman","heroine","superpowers","female","good","superpower","hero"]},"supervillain":{"a":"Supervillain","b":"1F9B9","j":["villain","marvel","criminal","superpower","evil"]},"man-supervillain":{"a":"Man Supervillain","b":"1F9B9-200D-2642-FE0F","j":["villain","male","man","criminal","superpowers","bad","superpower","evil","hero"]},"woman-supervillain":{"a":"Woman Supervillain","b":"1F9B9-200D-2640-FE0F","j":["villain","woman","heroine","criminal","female","superpowers","bad","superpower","evil"]},"mage":{"a":"Mage","b":"1F9D9","j":["sorcerer","wizard","witch","sorceress","magic"]},"man-mage":{"a":"Man Mage","b":"1F9D9-200D-2642-FE0F","j":["sorcerer","male","wizard","man","mage"]},"woman-mage":{"a":"Woman Mage","b":"1F9D9-200D-2640-FE0F","j":["woman","witch","sorceress","mage","female"]},"fairy":{"a":"Fairy","b":"1F9DA","j":["Titania","wings","Oberon","Puck","magical"]},"man-fairy":{"a":"Man Fairy","b":"1F9DA-200D-2642-FE0F","j":["man","Puck","male","Oberon"]},"woman-fairy":{"a":"Woman Fairy","b":"1F9DA-200D-2640-FE0F","j":["Titania","female","woman"]},"vampire":{"a":"Vampire","b":"1F9DB","j":["undead","blood","twilight","Dracula"]},"man-vampire":{"a":"Man Vampire","b":"1F9DB-200D-2642-FE0F","j":["male","Dracula","man","dracula","undead"]},"woman-vampire":{"a":"Woman Vampire","b":"1F9DB-200D-2640-FE0F","j":["woman","female","undead"]},"merperson":{"a":"Merperson","b":"1F9DC","j":["mermaid","sea","merman","merwoman"]},"merman":{"a":"Merman","b":"1F9DC-200D-2642-FE0F","j":["man","male","Triton","triton"]},"mermaid":{"a":"Mermaid","b":"1F9DC-200D-2640-FE0F","j":["woman","female","merwoman","ariel"]},"elf":{"a":"Elf","b":"1F9DD","j":["LOTR style","magical"]},"man-elf":{"a":"Man Elf","b":"1F9DD-200D-2642-FE0F","j":["man","male","magical"]},"woman-elf":{"a":"Woman Elf","b":"1F9DD-200D-2640-FE0F","j":["woman","female","magical"]},"genie":{"a":"Genie","b":"1F9DE","j":["(non-human color)","djinn","wishes","magical"]},"man-genie":{"a":"Man Genie","b":"1F9DE-200D-2642-FE0F","j":["man","male","djinn"]},"woman-genie":{"a":"Woman Genie","b":"1F9DE-200D-2640-FE0F","j":["woman","female","djinn"]},"zombie":{"a":"Zombie","b":"1F9DF","j":["(non-human color)","undead","walking dead","dead"]},"man-zombie":{"a":"Man Zombie","b":"1F9DF-200D-2642-FE0F","j":["walking dead","male","man","dracula","undead"]},"woman-zombie":{"a":"Woman Zombie","b":"1F9DF-200D-2640-FE0F","j":["woman","female","walking dead","undead"]},"person-getting-massage":{"a":"Person Getting Massage","b":"1F486","j":["relax","massage","face","salon"]},"man-getting-massage":{"a":"Man Getting Massage","b":"1F486-200D-2642-FE0F","j":["massage","male","man","head","boy","face"]},"woman-getting-massage":{"a":"Woman Getting Massage","b":"1F486-200D-2640-FE0F","j":["massage","woman","female","head","girl","face"]},"person-getting-haircut":{"a":"Person Getting Haircut","b":"1F487","j":["parlor","haircut","beauty","barber","hairstyle"]},"man-getting-haircut":{"a":"Man Getting Haircut","b":"1F487-200D-2642-FE0F","j":["man","boy","haircut","male"]},"woman-getting-haircut":{"a":"Woman Getting Haircut","b":"1F487-200D-2640-FE0F","j":["woman","female","haircut","girl"]},"person-walking":{"a":"Person Walking","b":"1F6B6","j":["walk","move","walking","hike"]},"man-walking":{"a":"Man Walking","b":"1F6B6-200D-2642-FE0F","j":["feet","walk","man","hike","human","steps"]},"woman-walking":{"a":"Woman Walking","b":"1F6B6-200D-2640-FE0F","j":["feet","walk","woman","hike","female","human","steps"]},"person-standing":{"a":"Person Standing","b":"1F9CD","j":["standing","stand","still"]},"man-standing":{"a":"Man Standing","b":"1F9CD-200D-2642-FE0F","j":["man","standing","still"]},"woman-standing":{"a":"Woman Standing","b":"1F9CD-200D-2640-FE0F","j":["standing","woman","still"]},"person-kneeling":{"a":"Person Kneeling","b":"1F9CE","j":["kneel","kneeling","pray","respectful"]},"man-kneeling":{"a":"Man Kneeling","b":"1F9CE-200D-2642-FE0F","j":["man","kneeling","pray","respectful"]},"woman-kneeling":{"a":"Woman Kneeling","b":"1F9CE-200D-2640-FE0F","j":["woman","pray","kneeling","respectful"]},"person-with-white-cane":{"a":"Person with White Cane","b":"1F9D1-200D-1F9AF","j":["accessibility","blind","person_with_probing_cane"]},"man-with-white-cane":{"a":"Man with White Cane","b":"1F468-200D-1F9AF","j":["man","accessibility","blind","man_with_probing_cane"]},"woman-with-white-cane":{"a":"Woman with White Cane","b":"1F469-200D-1F9AF","j":["woman","blind","woman_with_probing_cane","accessibility"]},"person-in-motorized-wheelchair":{"a":"Person in Motorized Wheelchair","b":"1F9D1-200D-1F9BC","j":["accessibility","disability","wheelchair"]},"man-in-motorized-wheelchair":{"a":"Man in Motorized Wheelchair","b":"1F468-200D-1F9BC","j":["man","accessibility","disability","wheelchair"]},"woman-in-motorized-wheelchair":{"a":"Woman in Motorized Wheelchair","b":"1F469-200D-1F9BC","j":["wheelchair","woman","disability","accessibility"]},"person-in-manual-wheelchair":{"a":"Person in Manual Wheelchair","b":"1F9D1-200D-1F9BD","j":["accessibility","disability","wheelchair"]},"man-in-manual-wheelchair":{"a":"Man in Manual Wheelchair","b":"1F468-200D-1F9BD","j":["man","accessibility","disability","wheelchair"]},"woman-in-manual-wheelchair":{"a":"Woman in Manual Wheelchair","b":"1F469-200D-1F9BD","j":["wheelchair","woman","disability","accessibility"]},"person-running":{"a":"Person Running","b":"1F3C3","j":["running","move","marathon"]},"man-running":{"a":"Man Running","b":"1F3C3-200D-2642-FE0F","j":["exercise","running","man","racing","race","marathon","walking"]},"woman-running":{"a":"Woman Running","b":"1F3C3-200D-2640-FE0F","j":["exercise","running","woman","racing","race","marathon","female","walking"]},"woman-dancing":{"a":"Woman Dancing","b":"1F483","j":["dance","woman","female","dancing","girl","fun"]},"man-dancing":{"a":"Man Dancing","b":"1F57A","j":["dancer","male","dance","man","dancing","boy","fun"]},"person-in-suit-levitating":{"a":"Person in Suit Levitating","b":"1F574","j":["business","jump","hover","levitate","suit","man_in_suit_levitating","person"]},"people-with-bunny-ears":{"a":"People with Bunny Ears","b":"1F46F","j":["dancer","partying","bunny ear","costume","perform"]},"men-with-bunny-ears":{"a":"Men with Bunny Ears","b":"1F46F-200D-2642-FE0F","j":["dancer","male","bunny","men","boys","partying","bunny ear"]},"women-with-bunny-ears":{"a":"Women with Bunny Ears","b":"1F46F-200D-2640-FE0F","j":["dancer","bunny","partying","female","bunny ear","women","girls"]},"person-in-steamy-room":{"a":"Person in Steamy Room","b":"1F9D6","j":["relax","sauna","spa","steambath","hamam","steam room"]},"man-in-steamy-room":{"a":"Man in Steamy Room","b":"1F9D6-200D-2642-FE0F","j":["sauna","male","spa","man","steamroom","steam room"]},"woman-in-steamy-room":{"a":"Woman in Steamy Room","b":"1F9D6-200D-2640-FE0F","j":["sauna","spa","woman","steamroom","female","steam room"]},"person-climbing":{"a":"Person Climbing","b":"1F9D7","j":["sport","climber"]},"man-climbing":{"a":"Man Climbing","b":"1F9D7-200D-2642-FE0F","j":["male","sports","man","rock","hobby","climber"]},"woman-climbing":{"a":"Woman Climbing","b":"1F9D7-200D-2640-FE0F","j":["sports","woman","rock","hobby","female","climber"]},"person-fencing":{"a":"Person Fencing","b":"1F93A","j":["sword","fencing","sports","fencer"]},"horse-racing":{"a":"Horse Racing","b":"1F3C7","j":["racehorse","racing","animal","competition","horse","luck","betting","gambling","jockey"]},"skier":{"a":"Skier","b":"26F7","j":["ski","winter","sports","snow"]},"snowboarder":{"a":"Snowboarder","b":"1F3C2","j":["sports","snow","winter","snowboard","ski"]},"person-golfing":{"a":"Person Golfing","b":"1F3CC","j":["business","golf","sports","ball"]},"man-golfing":{"a":"Man Golfing","b":"1F3CC-FE0F-200D-2642-FE0F","j":["golf","sport","man"]},"woman-golfing":{"a":"Woman Golfing","b":"1F3CC-FE0F-200D-2640-FE0F","j":["business","sports","golf","woman","female"]},"person-surfing":{"a":"Person Surfing","b":"1F3C4","j":["sport","sea","surfing"]},"man-surfing":{"a":"Man Surfing","b":"1F3C4-200D-2642-FE0F","j":["sports","man","sea","ocean","surfing","beach","summer"]},"woman-surfing":{"a":"Woman Surfing","b":"1F3C4-200D-2640-FE0F","j":["sports","woman","ocean","sea","female","surfing","beach","summer"]},"person-rowing-boat":{"a":"Person Rowing Boat","b":"1F6A3","j":["sport","rowboat","boat","move"]},"man-rowing-boat":{"a":"Man Rowing Boat","b":"1F6A3-200D-2642-FE0F","j":["rowboat","boat","sports","man","ship","water","hobby"]},"woman-rowing-boat":{"a":"Woman Rowing Boat","b":"1F6A3-200D-2640-FE0F","j":["rowboat","boat","sports","woman","water","ship","hobby","female"]},"person-swimming":{"a":"Person Swimming","b":"1F3CA","j":["swim","sport","pool"]},"man-swimming":{"a":"Man Swimming","b":"1F3CA-200D-2642-FE0F","j":["swim","exercise","athlete","sports","man","water","summer","human"]},"woman-swimming":{"a":"Woman Swimming","b":"1F3CA-200D-2640-FE0F","j":["swim","exercise","athlete","sports","woman","water","female","summer","human"]},"person-bouncing-ball":{"a":"Person Bouncing Ball","b":"26F9","j":["human","sports","ball"]},"man-bouncing-ball":{"a":"Man Bouncing Ball","b":"26F9-FE0F-200D-2642-FE0F","j":["man","sport","ball"]},"woman-bouncing-ball":{"a":"Woman Bouncing Ball","b":"26F9-FE0F-200D-2640-FE0F","j":["sports","woman","female","ball","human"]},"person-lifting-weights":{"a":"Person Lifting Weights","b":"1F3CB","j":["exercise","sports","lifter","training","weight"]},"man-lifting-weights":{"a":"Man Lifting Weights","b":"1F3CB-FE0F-200D-2642-FE0F","j":["man","sport","weight lifter"]},"woman-lifting-weights":{"a":"Woman Lifting Weights","b":"1F3CB-FE0F-200D-2640-FE0F","j":["exercise","sports","woman","female","training","weight lifter"]},"person-biking":{"a":"Person Biking","b":"1F6B4","j":["cyclist","sport","move","biking","bicycle"]},"man-biking":{"a":"Man Biking","b":"1F6B4-200D-2642-FE0F","j":["exercise","sports","cyclist","man","bike","bicycle","biking","hipster"]},"woman-biking":{"a":"Woman Biking","b":"1F6B4-200D-2640-FE0F","j":["exercise","sports","cyclist","woman","bike","bicycle","biking","hipster","female"]},"person-mountain-biking":{"a":"Person Mountain Biking","b":"1F6B5","j":["cyclist","sport","bike","move","bicycle","mountain","bicyclist"]},"man-mountain-biking":{"a":"Man Mountain Biking","b":"1F6B5-200D-2642-FE0F","j":["transportation","cyclist","man","bike","sports","race","bicycle","mountain","human"]},"woman-mountain-biking":{"a":"Woman Mountain Biking","b":"1F6B5-200D-2640-FE0F","j":["transportation","cyclist","sports","bike","woman","race","bicycle","biking","female","mountain","human"]},"person-cartwheeling":{"a":"Person Cartwheeling","b":"1F938","j":["cartwheel","gymnastic","gymnastics","sport"]},"man-cartwheeling":{"a":"Man Cartwheeling","b":"1F938-200D-2642-FE0F","j":["cartwheel","gymnastics","man"]},"woman-cartwheeling":{"a":"Woman Cartwheeling","b":"1F938-200D-2640-FE0F","j":["cartwheel","woman","gymnastics"]},"people-wrestling":{"a":"People Wrestling","b":"1F93C","j":["sport","wrestle","wrestler"]},"men-wrestling":{"a":"Men Wrestling","b":"1F93C-200D-2642-FE0F","j":["wrestle","men","sports","wrestlers"]},"women-wrestling":{"a":"Women Wrestling","b":"1F93C-200D-2640-FE0F","j":["wrestle","women","sports","wrestlers"]},"person-playing-water-polo":{"a":"Person Playing Water Polo","b":"1F93D","j":["sport","water","polo"]},"man-playing-water-polo":{"a":"Man Playing Water Polo","b":"1F93D-200D-2642-FE0F","j":["man","water polo","sports","pool"]},"woman-playing-water-polo":{"a":"Woman Playing Water Polo","b":"1F93D-200D-2640-FE0F","j":["woman","water polo","sports","pool"]},"person-playing-handball":{"a":"Person Playing Handball","b":"1F93E","j":["sport","handball","ball"]},"man-playing-handball":{"a":"Man Playing Handball","b":"1F93E-200D-2642-FE0F","j":["man","handball","sports"]},"woman-playing-handball":{"a":"Woman Playing Handball","b":"1F93E-200D-2640-FE0F","j":["woman","handball","sports"]},"person-juggling":{"a":"Person Juggling","b":"1F939","j":["balance","juggle","skill","multitask","performance"]},"man-juggling":{"a":"Man Juggling","b":"1F939-200D-2642-FE0F","j":["balance","man","juggle","juggling","skill","multitask"]},"woman-juggling":{"a":"Woman Juggling","b":"1F939-200D-2640-FE0F","j":["balance","woman","juggle","juggling","skill","multitask"]},"person-in-lotus-position":{"a":"Person in Lotus Position","b":"1F9D8","j":["meditation","yoga","serenity","meditate"]},"man-in-lotus-position":{"a":"Man in Lotus Position","b":"1F9D8-200D-2642-FE0F","j":["meditation","yoga","male","man","mindfulness","serenity","zen"]},"woman-in-lotus-position":{"a":"Woman in Lotus Position","b":"1F9D8-200D-2640-FE0F","j":["meditation","yoga","woman","female","mindfulness","serenity","zen"]},"person-taking-bath":{"a":"Person Taking Bath","b":"1F6C0","j":["bath","bathtub","bathroom","clean","shower"]},"person-in-bed":{"a":"Person in Bed","b":"1F6CC","j":["rest","hotel","bed","sleep"]},"people-holding-hands":{"a":"People Holding Hands","b":"1F9D1-200D-1F91D-200D-1F9D1","j":["friendship","couple","hold","holding hands","hand","person"]},"women-holding-hands":{"a":"Women Holding Hands","b":"1F46D","j":["hand","pair","friendship","couple","love","like","female","holding hands","people","women","human"]},"woman-and-man-holding-hands":{"a":"Woman and Man Holding Hands","b":"1F46B","j":["dating","man","marriage","couple","love","like","valentines","hold","people","hand","human","affection","pair","woman","holding hands","date"]},"men-holding-hands":{"a":"Men Holding Hands","b":"1F46C","j":["Gemini","men","pair","man","twins","friendship","couple","love","like","holding hands","people","human","zodiac","bromance"]},"kiss":{"a":"Kiss","b":"1F48F","j":["pair","marriage","love","couple","like","valentines","dating"]},"kiss-woman-man":{"a":"Kiss: Woman, Man","b":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F468","j":["man","woman","kiss","love","couple"]},"kiss-man-man":{"a":"Kiss: Man, Man","b":"1F468-200D-2764-FE0F-200D-1F48B-200D-1F468","j":["pair","man","marriage","kiss","couple","love","like","valentines","dating"]},"kiss-woman-woman":{"a":"Kiss: Woman, Woman","b":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F469","j":["pair","woman","marriage","kiss","couple","love","like","valentines","dating"]},"couple-with-heart":{"a":"Couple with Heart","b":"1F491","j":["pair","marriage","love","couple","like","valentines","dating","human","affection"]},"couple-with-heart-woman-man":{"a":"Couple with Heart: Woman, Man","b":"1F469-200D-2764-FE0F-200D-1F468","j":["man","woman","love","couple with heart","couple"]},"couple-with-heart-man-man":{"a":"Couple with Heart: Man, Man","b":"1F468-200D-2764-FE0F-200D-1F468","j":["pair","man","marriage","love","couple","couple with heart","like","valentines","dating","human","affection"]},"couple-with-heart-woman-woman":{"a":"Couple with Heart: Woman, Woman","b":"1F469-200D-2764-FE0F-200D-1F469","j":["pair","woman","marriage","love","couple","couple with heart","like","valentines","dating","human","affection"]},"family":{"a":"Family","b":"1F46A","j":["mom","mother","dad","father","parents","child","home","people","human"]},"family-man-woman-boy":{"a":"Family: Man, Woman, Boy","b":"1F468-200D-1F469-200D-1F466","j":["man","woman","family","love","boy"]},"family-man-woman-girl":{"a":"Family: Man, Woman, Girl","b":"1F468-200D-1F469-200D-1F467","j":["parents","man","woman","family","child","girl","home","people","human"]},"family-man-woman-girl-boy":{"a":"Family: Man, Woman, Girl, Boy","b":"1F468-200D-1F469-200D-1F467-200D-1F466","j":["children","parents","man","woman","family","girl","home","people","boy","human"]},"family-man-woman-boy-boy":{"a":"Family: Man, Woman, Boy, Boy","b":"1F468-200D-1F469-200D-1F466-200D-1F466","j":["children","parents","man","woman","family","home","people","boy","human"]},"family-man-woman-girl-girl":{"a":"Family: Man, Woman, Girl, Girl","b":"1F468-200D-1F469-200D-1F467-200D-1F467","j":["children","parents","man","woman","family","girl","home","people","human"]},"family-man-man-boy":{"a":"Family: Man, Man, Boy","b":"1F468-200D-1F468-200D-1F466","j":["children","parents","man","family","home","people","boy","human"]},"family-man-man-girl":{"a":"Family: Man, Man, Girl","b":"1F468-200D-1F468-200D-1F467","j":["children","parents","man","family","girl","home","people","human"]},"family-man-man-girl-boy":{"a":"Family: Man, Man, Girl, Boy","b":"1F468-200D-1F468-200D-1F467-200D-1F466","j":["children","parents","man","family","girl","home","people","boy","human"]},"family-man-man-boy-boy":{"a":"Family: Man, Man, Boy, Boy","b":"1F468-200D-1F468-200D-1F466-200D-1F466","j":["children","parents","man","family","home","people","boy","human"]},"family-man-man-girl-girl":{"a":"Family: Man, Man, Girl, Girl","b":"1F468-200D-1F468-200D-1F467-200D-1F467","j":["children","parents","man","family","girl","home","people","human"]},"family-woman-woman-boy":{"a":"Family: Woman, Woman, Boy","b":"1F469-200D-1F469-200D-1F466","j":["children","parents","woman","family","home","people","boy","human"]},"family-woman-woman-girl":{"a":"Family: Woman, Woman, Girl","b":"1F469-200D-1F469-200D-1F467","j":["children","parents","woman","family","girl","home","people","human"]},"family-woman-woman-girl-boy":{"a":"Family: Woman, Woman, Girl, Boy","b":"1F469-200D-1F469-200D-1F467-200D-1F466","j":["children","parents","woman","family","girl","home","people","boy","human"]},"family-woman-woman-boy-boy":{"a":"Family: Woman, Woman, Boy, Boy","b":"1F469-200D-1F469-200D-1F466-200D-1F466","j":["children","parents","woman","family","home","people","boy","human"]},"family-woman-woman-girl-girl":{"a":"Family: Woman, Woman, Girl, Girl","b":"1F469-200D-1F469-200D-1F467-200D-1F467","j":["children","parents","woman","family","girl","home","people","human"]},"family-man-boy":{"a":"Family: Man, Boy","b":"1F468-200D-1F466","j":["man","parent","family","child","home","people","boy","human"]},"family-man-boy-boy":{"a":"Family: Man, Boy, Boy","b":"1F468-200D-1F466-200D-1F466","j":["children","man","parent","family","home","people","boy","human"]},"family-man-girl":{"a":"Family: Man, Girl","b":"1F468-200D-1F467","j":["man","parent","family","child","girl","home","people","human"]},"family-man-girl-boy":{"a":"Family: Man, Girl, Boy","b":"1F468-200D-1F467-200D-1F466","j":["children","man","parent","family","girl","home","people","boy","human"]},"family-man-girl-girl":{"a":"Family: Man, Girl, Girl","b":"1F468-200D-1F467-200D-1F467","j":["children","man","parent","family","girl","home","people","human"]},"family-woman-boy":{"a":"Family: Woman, Boy","b":"1F469-200D-1F466","j":["woman","family","parent","child","home","people","boy","human"]},"family-woman-boy-boy":{"a":"Family: Woman, Boy, Boy","b":"1F469-200D-1F466-200D-1F466","j":["children","woman","family","parent","home","people","boy","human"]},"family-woman-girl":{"a":"Family: Woman, Girl","b":"1F469-200D-1F467","j":["woman","family","parent","child","girl","home","people","human"]},"family-woman-girl-boy":{"a":"Family: Woman, Girl, Boy","b":"1F469-200D-1F467-200D-1F466","j":["children","woman","family","parent","girl","home","people","boy","human"]},"family-woman-girl-girl":{"a":"Family: Woman, Girl, Girl","b":"1F469-200D-1F467-200D-1F467","j":["children","woman","family","parent","girl","home","people","human"]},"speaking-head":{"a":"Speaking Head","b":"1F5E3","j":["speak","talk","say","sing","user","face","head","silhouette","speaking","human","person"]},"bust-in-silhouette":{"a":"Bust in Silhouette","b":"1F464","j":["bust","user","silhouette","human","person"]},"busts-in-silhouette":{"a":"Busts in Silhouette","b":"1F465","j":["group","bust","user","team","silhouette","human","person"]},"people-hugging":{"a":"People Hugging","b":"1FAC2","j":["thanks","hug","goodbye","hello","care"]},"footprints":{"a":"Footprints","b":"1F463","j":["clothing","print","tracking","feet","footprint","walking","beach"]},"red-hair":{"a":"Red Hair","b":"1F9B0","j":["ginger","red hair","redhead"]},"curly-hair":{"a":"Curly Hair","b":"1F9B1","j":["afro","curly","curly hair","ringlets"]},"white-hair":{"a":"White Hair","b":"1F9B3","j":["gray","hair","old","white"]},"bald":{"a":"Bald","b":"1F9B2","j":["bald","chemotherapy","hairless","no hair","shaven"]},"monkey-face":{"a":"Monkey Face","b":"1F435","j":["animal","circus","face","nature","monkey"]},"monkey":{"a":"Monkey","b":"1F412","j":["banana","animal","circus","nature"]},"gorilla":{"a":"Gorilla","b":"1F98D","j":["animal","circus","nature"]},"orangutan":{"a":"Orangutan","b":"1F9A7","j":["ape","animal"]},"dog-face":{"a":"Dog Face","b":"1F436","j":["animal","dog","pet","friend","nature","puppy","faithful","face","woof"]},"dog":{"a":"Dog","b":"1F415","j":["animal","pet","friend","nature","doge","faithful"]},"guide-dog":{"a":"Guide Dog","b":"1F9AE","j":["accessibility","animal","blind","guide"]},"service-dog":{"a":"Service Dog","b":"1F415-200D-1F9BA","j":["blind","animal","assistance","dog","accessibility","service"]},"poodle":{"a":"Poodle","b":"1F429","j":["animal","dog","pet","nature","101"]},"wolf":{"a":"Wolf","b":"1F43A","j":["animal","face","wild","nature"]},"fox":{"a":"Fox","b":"1F98A","j":["animal","face","nature"]},"raccoon":{"a":"Raccoon","b":"1F99D","j":["curious","animal","sly","nature"]},"cat-face":{"a":"Cat Face","b":"1F431","j":["meow","animal","pet","nature","kitten","cat","face"]},"cat":{"a":"Cat","b":"1F408","j":["animal","pet","cats","meow"]},"black-cat":{"a":"Black Cat","b":"1F408-200D-2B1B","j":["black","unlucky","luck","cat","superstition"]},"lion":{"a":"Lion","b":"1F981","j":["animal","Leo","nature","face","zodiac"]},"tiger-face":{"a":"Tiger Face","b":"1F42F","j":["tiger","animal","roar","wild","nature","danger","cat","face"]},"tiger":{"a":"Tiger","b":"1F405","j":["roar","animal","nature"]},"leopard":{"a":"Leopard","b":"1F406","j":["animal","nature"]},"horse-face":{"a":"Horse Face","b":"1F434","j":["brown","animal","horse","nature","face"]},"horse":{"a":"Horse","b":"1F40E","j":["racehorse","equestrian","animal","racing","luck","gamble"]},"unicorn":{"a":"Unicorn","b":"1F984","j":["nature","animal","mystical","face"]},"zebra":{"a":"Zebra","b":"1F993","j":["safari","stripes","animal","stripe","nature"]},"deer":{"a":"Deer","b":"1F98C","j":["venison","animal","horns","nature"]},"bison":{"a":"Bison","b":"1F9AC","j":["ox","herd","buffalo","wisent"]},"cow-face":{"a":"Cow Face","b":"1F42E","j":["nature","ox","cow","animal","beef","milk","moo","face"]},"ox":{"a":"Ox","b":"1F402","j":["Taurus","animal","cow","beef","bull","zodiac"]},"water-buffalo":{"a":"Water Buffalo","b":"1F403","j":["ox","water","animal","cow","buffalo","nature"]},"cow":{"a":"Cow","b":"1F404","j":["ox","animal","moo","beef","milk","nature"]},"pig-face":{"a":"Pig Face","b":"1F437","j":["pig","animal","nature","face","oink"]},"pig":{"a":"Pig","b":"1F416","j":["animal","sow","nature"]},"boar":{"a":"Boar","b":"1F417","j":["pig","animal","nature"]},"pig-nose":{"a":"Pig Nose","b":"1F43D","j":["pig","animal","nose","face","oink"]},"ram":{"a":"Ram","b":"1F40F","j":["Aries","sheep","male","animal","nature","zodiac"]},"ewe":{"a":"Ewe","b":"1F411","j":["sheep","shipit","animal","wool","female","nature"]},"goat":{"a":"Goat","b":"1F410","j":["Capricorn","animal","zodiac","nature"]},"camel":{"a":"Camel","b":"1F42A","j":["dromedary","hot","animal","desert","hump"]},"twohump-camel":{"a":"Two-Hump Camel","b":"1F42B","j":["camel","two_hump_camel","hot","animal","two-hump camel","desert","nature","bactrian","hump"]},"llama":{"a":"Llama","b":"1F999","j":["alpaca","nature","animal","wool","vicuña","guanaco"]},"giraffe":{"a":"Giraffe","b":"1F992","j":["nature","spots","animal","safari"]},"elephant":{"a":"Elephant","b":"1F418","j":["animal","nature","th","nose","circus"]},"mammoth":{"a":"Mammoth","b":"1F9A3","j":["tusk","extinction","large","woolly","elephant","tusks"]},"rhinoceros":{"a":"Rhinoceros","b":"1F98F","j":["nature","animal","horn"]},"hippopotamus":{"a":"Hippopotamus","b":"1F99B","j":["hippo","animal","nature"]},"mouse-face":{"a":"Mouse Face","b":"1F42D","j":["mouse","animal","cheese_wedge","nature","face","rodent"]},"mouse":{"a":"Mouse","b":"1F401","j":["animal","rodent","nature"]},"rat":{"a":"Rat","b":"1F400","j":["mouse","animal","rodent"]},"hamster":{"a":"Hamster","b":"1F439","j":["pet","animal","face","nature"]},"rabbit-face":{"a":"Rabbit Face","b":"1F430","j":["bunny","animal","spring","magic","pet","rabbit","nature","face"]},"rabbit":{"a":"Rabbit","b":"1F407","j":["bunny","animal","spring","magic","pet","nature"]},"chipmunk":{"a":"Chipmunk","b":"1F43F","j":["animal","squirrel","rodent","nature"]},"beaver":{"a":"Beaver","b":"1F9AB","j":["animal","dam","rodent"]},"hedgehog":{"a":"Hedgehog","b":"1F994","j":["animal","spiny","nature"]},"bat":{"a":"Bat","b":"1F987","j":["blind","vampire","animal","nature"]},"bear":{"a":"Bear","b":"1F43B","j":["animal","face","wild","nature"]},"polar-bear":{"a":"Polar Bear","b":"1F43B-200D-2744-FE0F","j":["bear","animal","white","arctic"]},"koala":{"a":"Koala","b":"1F428","j":["bear","animal","nature"]},"panda":{"a":"Panda","b":"1F43C","j":["animal","face","nature"]},"sloth":{"a":"Sloth","b":"1F9A5","j":["slow","lazy","animal"]},"otter":{"a":"Otter","b":"1F9A6","j":["fishing","animal","playful"]},"skunk":{"a":"Skunk","b":"1F9A8","j":["stink","animal"]},"kangaroo":{"a":"Kangaroo","b":"1F998","j":["jump","hop","australia","marsupial","animal","Australia","nature","joey"]},"badger":{"a":"Badger","b":"1F9A1","j":["animal","pester","nature","honey","honey badger"]},"paw-prints":{"a":"Paw Prints","b":"1F43E","j":["print","tracking","feet","cat","animal","paw","dog","pet","footprints"]},"turkey":{"a":"Turkey","b":"1F983","j":["animal","bird"]},"chicken":{"a":"Chicken","b":"1F414","j":["animal","cluck","bird","nature"]},"rooster":{"a":"Rooster","b":"1F413","j":["nature","animal","chicken","bird"]},"hatching-chick":{"a":"Hatching Chick","b":"1F423","j":["animal","chick","egg","born","baby","bird","hatching","chicken"]},"baby-chick":{"a":"Baby Chick","b":"1F424","j":["animal","chick","baby","bird","chicken"]},"frontfacing-baby-chick":{"a":"Front-Facing Baby Chick","b":"1F425","j":["chicken","animal","front_facing_baby_chick","chick","front-facing baby chick","baby","bird"]},"bird":{"a":"Bird","b":"1F426","j":["fly","animal","spring","tweet","nature"]},"penguin":{"a":"Penguin","b":"1F427","j":["nature","animal","bird"]},"dove":{"a":"Dove","b":"1F54A","j":["animal","fly","peace","bird"]},"eagle":{"a":"Eagle","b":"1F985","j":["nature","animal","bird"]},"duck":{"a":"Duck","b":"1F986","j":["nature","animal","mallard","bird"]},"swan":{"a":"Swan","b":"1F9A2","j":["nature","animal","ugly duckling","cygnet","bird"]},"owl":{"a":"Owl","b":"1F989","j":["wise","nature","animal","bird","hoot"]},"dodo":{"a":"Dodo","b":"1F9A4","j":["extinction","Mauritius","animal","large","bird"]},"feather":{"a":"Feather","b":"1FAB6","j":["fly","flight","plumage","light","bird"]},"flamingo":{"a":"Flamingo","b":"1F9A9","j":["animal","flamboyant","tropical"]},"peacock":{"a":"Peacock","b":"1F99A","j":["ostentatious","animal","peahen","proud","nature","bird"]},"parrot":{"a":"Parrot","b":"1F99C","j":["nature","animal","bird","talk","pirate"]},"frog":{"a":"Frog","b":"1F438","j":["animal","toad","croak","nature","face"]},"crocodile":{"a":"Crocodile","b":"1F40A","j":["alligator","animal","nature","reptile","lizard"]},"turtle":{"a":"Turtle","b":"1F422","j":["slow","animal","tortoise","terrapin","nature"]},"lizard":{"a":"Lizard","b":"1F98E","j":["reptile","animal","nature"]},"snake":{"a":"Snake","b":"1F40D","j":["python","animal","bearer","Ophiuchus","hiss","nature","serpent","evil","zodiac"]},"dragon-face":{"a":"Dragon Face","b":"1F432","j":["dragon","fairy tale","myth","animal","green","chinese","nature","face"]},"dragon":{"a":"Dragon","b":"1F409","j":["fairy tale","myth","animal","green","chinese","nature"]},"sauropod":{"a":"Sauropod","b":"1F995","j":["dinosaur","brontosaurus","animal","brachiosaurus","diplodocus","nature","extinct"]},"trex":{"a":"T-Rex","b":"1F996","j":["dinosaur","t_rex","animal","Tyrannosaurus Rex","tyrannosaurus","nature","extinct"]},"spouting-whale":{"a":"Spouting Whale","b":"1F433","j":["nature","sea","ocean","animal","spouting","whale","face"]},"whale":{"a":"Whale","b":"1F40B","j":["sea","ocean","animal","nature"]},"dolphin":{"a":"Dolphin","b":"1F42C","j":["fish","sea","ocean","animal","flipper","fins","beach","nature"]},"seal":{"a":"Seal","b":"1F9AD","j":["sea","animal","sea lion","creature"]},"fish":{"a":"Fish","b":"1F41F","j":["Pisces","food","animal","nature","zodiac"]},"tropical-fish":{"a":"Tropical Fish","b":"1F420","j":["swim","fish","nemo","ocean","animal","beach","tropical"]},"blowfish":{"a":"Blowfish","b":"1F421","j":["fish","food","sea","ocean","animal","nature"]},"shark":{"a":"Shark","b":"1F988","j":["fish","sea","ocean","animal","fins","beach","nature","jaws"]},"octopus":{"a":"Octopus","b":"1F419","j":["sea","ocean","animal","beach","nature","creature"]},"spiral-shell":{"a":"Spiral Shell","b":"1F41A","j":["sea","beach","nature","spiral","shell"]},"snail":{"a":"Snail","b":"1F40C","j":["slow","animal","shell"]},"butterfly":{"a":"Butterfly","b":"1F98B","j":["pretty","animal","caterpillar","nature","insect"]},"bug":{"a":"Bug","b":"1F41B","j":["nature","animal","worm","insect"]},"ant":{"a":"Ant","b":"1F41C","j":["nature","bug","animal","insect"]},"honeybee":{"a":"Honeybee","b":"1F41D","j":["bee","animal","spring","nature","honey","bug","insect"]},"beetle":{"a":"Beetle","b":"1FAB2","j":["bug","insect"]},"lady-beetle":{"a":"Lady Beetle","b":"1F41E","j":["ladybird","beetle","animal","ladybug","nature","insect"]},"cricket":{"a":"Cricket","b":"1F997","j":["chirp","animal","grasshopper","Orthoptera"]},"cockroach":{"a":"Cockroach","b":"1FAB3","j":["pest","roach","pests","insect"]},"spider":{"a":"Spider","b":"1F577","j":["arachnid","animal","insect"]},"spider-web":{"a":"Spider Web","b":"1F578","j":["spider","web","animal","arachnid","silk","insect"]},"scorpion":{"a":"Scorpion","b":"1F982","j":["Scorpio","animal","scorpio","arachnid","zodiac"]},"mosquito":{"a":"Mosquito","b":"1F99F","j":["virus","animal","fever","disease","malaria","nature","pest","insect"]},"fly":{"a":"Fly","b":"1FAB0","j":["maggot","disease","pest","rotting","insect"]},"worm":{"a":"Worm","b":"1FAB1","j":["annelid","earthworm","animal","parasite"]},"microbe":{"a":"Microbe","b":"1F9A0","j":["amoeba","virus","bacteria","germs"]},"bouquet":{"a":"Bouquet","b":"1F490","j":["flowers","spring","flower","nature"]},"cherry-blossom":{"a":"Cherry Blossom","b":"1F338","j":["blossom","spring","flower","nature","plant","cherry"]},"white-flower":{"a":"White Flower","b":"1F4AE","j":["japanese","spring","flower"]},"rosette":{"a":"Rosette","b":"1F3F5","j":["plant","military","flower","decoration"]},"rose":{"a":"Rose","b":"1F339","j":["flowers","spring","love","valentines","flower"]},"wilted-flower":{"a":"Wilted Flower","b":"1F940","j":["plant","wilted","flower","nature"]},"hibiscus":{"a":"Hibiscus","b":"1F33A","j":["flowers","vegetable","beach","flower","plant"]},"sunflower":{"a":"Sunflower","b":"1F33B","j":["sun","flower","nature","plant","fall"]},"blossom":{"a":"Blossom","b":"1F33C","j":["flowers","yellow","flower","nature"]},"tulip":{"a":"Tulip","b":"1F337","j":["flowers","spring","flower","nature","summer","plant"]},"seedling":{"a":"Seedling","b":"1F331","j":["lawn","spring","young","nature","plant","grass"]},"potted-plant":{"a":"Potted Plant","b":"1FAB4","j":["useless","boring","greenery","house","nurturing","plant","grow"]},"evergreen-tree":{"a":"Evergreen Tree","b":"1F332","j":["plant","tree","nature"]},"deciduous-tree":{"a":"Deciduous Tree","b":"1F333","j":["deciduous","shedding","tree","nature","plant"]},"palm-tree":{"a":"Palm Tree","b":"1F334","j":["mojito","vegetable","palm","beach","tree","nature","summer","plant","tropical"]},"cactus":{"a":"Cactus","b":"1F335","j":["plant","vegetable","nature"]},"sheaf-of-rice":{"a":"Sheaf of Rice","b":"1F33E","j":["rice","grain","nature","ear","plant"]},"herb":{"a":"Herb","b":"1F33F","j":["lawn","vegetable","medicine","leaf","plant","weed","grass"]},"shamrock":{"a":"Shamrock","b":"2618","j":["irish","vegetable","clover","nature","plant"]},"four-leaf-clover":{"a":"Four Leaf Clover","b":"1F340","j":["lucky","irish","four","vegetable","clover","four-leaf clover","leaf","4","nature","plant"]},"maple-leaf":{"a":"Maple Leaf","b":"1F341","j":["ca","vegetable","leaf","falling","nature","plant","maple","fall"]},"fallen-leaf":{"a":"Fallen Leaf","b":"1F342","j":["vegetable","leaf","leaves","falling","nature","plant"]},"leaf-fluttering-in-wind":{"a":"Leaf Fluttering in Wind","b":"1F343","j":["grass","lawn","vegetable","leaf","spring","flutter","wind","tree","nature","plant","blow"]},"grapes":{"a":"Grapes","b":"1F347","j":["wine","food","grape","fruit"]},"melon":{"a":"Melon","b":"1F348","j":["food","fruit","nature"]},"watermelon":{"a":"Watermelon","b":"1F349","j":["summer","food","picnic","fruit"]},"tangerine":{"a":"Tangerine","b":"1F34A","j":["food","orange","fruit","nature"]},"lemon":{"a":"Lemon","b":"1F34B","j":["fruit","citrus","nature"]},"banana":{"a":"Banana","b":"1F34C","j":["food","monkey","fruit"]},"pineapple":{"a":"Pineapple","b":"1F34D","j":["food","fruit","nature"]},"mango":{"a":"Mango","b":"1F96D","j":["food","fruit","tropical"]},"red-apple":{"a":"Red Apple","b":"1F34E","j":["red","fruit","school","mac","apple"]},"green-apple":{"a":"Green Apple","b":"1F34F","j":["green","fruit","apple","nature"]},"pear":{"a":"Pear","b":"1F350","j":["food","fruit","nature"]},"peach":{"a":"Peach","b":"1F351","j":["food","fruit","nature"]},"cherries":{"a":"Cherries","b":"1F352","j":["food","red","fruit","berries","cherry"]},"strawberry":{"a":"Strawberry","b":"1F353","j":["food","nature","berry","fruit"]},"blueberries":{"a":"Blueberries","b":"1FAD0","j":["fruit","berry","blue","bilberry","blueberry"]},"kiwi-fruit":{"a":"Kiwi Fruit","b":"1F95D","j":["food","kiwi","fruit"]},"tomato":{"a":"Tomato","b":"1F345","j":["food","fruit","vegetable","nature"]},"olive":{"a":"Olive","b":"1FAD2","j":["food","fruit"]},"coconut":{"a":"Coconut","b":"1F965","j":["food","fruit","palm","nature","piña colada"]},"avocado":{"a":"Avocado","b":"1F951","j":["food","fruit"]},"eggplant":{"a":"Eggplant","b":"1F346","j":["food","aubergine","vegetable","nature"]},"potato":{"a":"Potato","b":"1F954","j":["food","tuber","vegetable","vegatable","starch"]},"carrot":{"a":"Carrot","b":"1F955","j":["food","vegetable","orange"]},"ear-of-corn":{"a":"Ear of Corn","b":"1F33D","j":["food","vegetable","corn","maize","maze","ear","plant"]},"hot-pepper":{"a":"Hot Pepper","b":"1F336","j":["food","hot","spicy","chili","pepper","chilli"]},"bell-pepper":{"a":"Bell Pepper","b":"1FAD1","j":["fruit","vegetable","capsicum","pepper","plant"]},"cucumber":{"a":"Cucumber","b":"1F952","j":["food","pickle","vegetable","fruit"]},"leafy-green":{"a":"Leafy Green","b":"1F96C","j":["food","vegetable","cabbage","kale","plant","lettuce","bok choy"]},"broccoli":{"a":"Broccoli","b":"1F966","j":["food","wild cabbage","fruit","vegetable"]},"garlic":{"a":"Garlic","b":"1F9C4","j":["cook","food","spice","flavoring"]},"onion":{"a":"Onion","b":"1F9C5","j":["cook","food","spice","flavoring"]},"mushroom":{"a":"Mushroom","b":"1F344","j":["toadstool","plant","vegetable"]},"peanuts":{"a":"Peanuts","b":"1F95C","j":["nut","food","peanut","vegetable"]},"chestnut":{"a":"Chestnut","b":"1F330","j":["plant","squirrel","food"]},"bread":{"a":"Bread","b":"1F35E","j":["food","wheat","loaf","breakfast","toast"]},"croissant":{"a":"Croissant","b":"1F950","j":["food","bread","breakfast","french","roll"]},"baguette-bread":{"a":"Baguette Bread","b":"1F956","j":["food","bread","french","baguette"]},"flatbread":{"a":"Flatbread","b":"1FAD3","j":["food","pita","naan","arepa","lavash","flour"]},"pretzel":{"a":"Pretzel","b":"1F968","j":["twisted","food","bread","convoluted"]},"bagel":{"a":"Bagel","b":"1F96F","j":["food","bread","breakfast","schmear","bakery"]},"pancakes":{"a":"Pancakes","b":"1F95E","j":["flapjacks","food","pancake","hotcakes","breakfast","crêpe","hotcake"]},"waffle":{"a":"Waffle","b":"1F9C7","j":["food","breakfast","indecisive","iron"]},"cheese-wedge":{"a":"Cheese Wedge","b":"1F9C0","j":["food","chadder","cheese"]},"meat-on-bone":{"a":"Meat on Bone","b":"1F356","j":["food","drumstick","bone","good","meat"]},"poultry-leg":{"a":"Poultry Leg","b":"1F357","j":["food","turkey","drumstick","bone","leg","poultry","bird","meat","chicken"]},"cut-of-meat":{"a":"Cut of Meat","b":"1F969","j":["food","porkchop","chop","cow","cut","lambchop","steak","meat"]},"bacon":{"a":"Bacon","b":"1F953","j":["pig","food","pork","breakfast","meat"]},"hamburger":{"a":"Hamburger","b":"1F354","j":["burger","cheeseburger","beef","fast food","burger king","meat","mcdonalds"]},"french-fries":{"a":"French Fries","b":"1F35F","j":["fries","fast food","french","chips","snack"]},"pizza":{"a":"Pizza","b":"1F355","j":["food","cheese","party","slice"]},"hot-dog":{"a":"Hot Dog","b":"1F32D","j":["hotdog","food","sausage","frankfurter"]},"sandwich":{"a":"Sandwich","b":"1F96A","j":["lunch","bread","food"]},"taco":{"a":"Taco","b":"1F32E","j":["mexican","food"]},"burrito":{"a":"Burrito","b":"1F32F","j":["wrap","mexican","food"]},"tamale":{"a":"Tamale","b":"1FAD4","j":["mexican","food","wrapped","masa"]},"stuffed-flatbread":{"a":"Stuffed Flatbread","b":"1F959","j":["food","kebab","stuffed","gyro","falafel","flatbread"]},"falafel":{"a":"Falafel","b":"1F9C6","j":["chickpea","food","meatball"]},"egg":{"a":"Egg","b":"1F95A","j":["food","chicken","breakfast"]},"cooking":{"a":"Cooking","b":"1F373","j":["food","frying","kitchen","breakfast","egg","pan"]},"shallow-pan-of-food":{"a":"Shallow Pan of Food","b":"1F958","j":["food","casserole","cooking","paella","pan","shallow"]},"pot-of-food":{"a":"Pot of Food","b":"1F372","j":["food","stew","pot","soup","meat"]},"fondue":{"a":"Fondue","b":"1FAD5","j":["food","pot","chocolate","melted","Swiss","cheese"]},"bowl-with-spoon":{"a":"Bowl with Spoon","b":"1F963","j":["food","congee","porridge","breakfast","cereal","oatmeal"]},"green-salad":{"a":"Green Salad","b":"1F957","j":["food","healthy","green","lettuce","salad"]},"popcorn":{"a":"Popcorn","b":"1F37F","j":["movie theater","food","films","snack"]},"butter":{"a":"Butter","b":"1F9C8","j":["cook","food","dairy"]},"salt":{"a":"Salt","b":"1F9C2","j":["shaker","condiment"]},"canned-food":{"a":"Canned Food","b":"1F96B","j":["soup","food","can"]},"bento-box":{"a":"Bento Box","b":"1F371","j":["bento","food","japanese","box"]},"rice-cracker":{"a":"Rice Cracker","b":"1F358","j":["rice","japanese","food","cracker"]},"rice-ball":{"a":"Rice Ball","b":"1F359","j":["food","Japanese","rice","ball","japanese"]},"cooked-rice":{"a":"Cooked Rice","b":"1F35A","j":["food","china","rice","asian","cooked"]},"curry-rice":{"a":"Curry Rice","b":"1F35B","j":["food","hot","spicy","curry","rice","indian"]},"steaming-bowl":{"a":"Steaming Bowl","b":"1F35C","j":["food","noodle","steaming","bowl","chopsticks","japanese","ramen"]},"spaghetti":{"a":"Spaghetti","b":"1F35D","j":["pasta","italian","noodle","food"]},"roasted-sweet-potato":{"a":"Roasted Sweet Potato","b":"1F360","j":["food","potato","roasted","nature","sweet"]},"oden":{"a":"Oden","b":"1F362","j":["food","kebab","seafood","skewer","japanese","stick"]},"sushi":{"a":"Sushi","b":"1F363","j":["rice","fish","food","japanese"]},"fried-shrimp":{"a":"Fried Shrimp","b":"1F364","j":["tempura","food","shrimp","animal","prawn","appetizer","summer","fried"]},"fish-cake-with-swirl":{"a":"Fish Cake with Swirl","b":"1F365","j":["fish","food","pink","kamaboko","sea","pastry","narutomaki","cake","swirl","beach","japan","ramen","surimi"]},"moon-cake":{"a":"Moon Cake","b":"1F96E","j":["yuèbǐng","autumn","food","festival"]},"dango":{"a":"Dango","b":"1F361","j":["food","stick","Japanese","skewer","japanese","dessert","sweet","barbecue","meat"]},"dumpling":{"a":"Dumpling","b":"1F95F","j":["food","empanada","jiaozi","potsticker","gyōza","pierogi"]},"fortune-cookie":{"a":"Fortune Cookie","b":"1F960","j":["food","prophecy"]},"takeout-box":{"a":"Takeout Box","b":"1F961","j":["leftovers","food","oyster pail"]},"crab":{"a":"Crab","b":"1F980","j":["Cancer","animal","crustacean","zodiac"]},"lobster":{"a":"Lobster","b":"1F99E","j":["bisque","animal","seafood","claws","nature"]},"shrimp":{"a":"Shrimp","b":"1F990","j":["food","shellfish","ocean","animal","small","seafood","nature"]},"squid":{"a":"Squid","b":"1F991","j":["food","sea","ocean","animal","nature","molusc"]},"oyster":{"a":"Oyster","b":"1F9AA","j":["diving","food","pearl"]},"soft-ice-cream":{"a":"Soft Ice Cream","b":"1F366","j":["food","soft","icecream","ice","hot","dessert","summer","sweet","cream"]},"shaved-ice":{"a":"Shaved Ice","b":"1F367","j":["ice","shaved","hot","dessert","summer","sweet"]},"ice-cream":{"a":"Ice Cream","b":"1F368","j":["food","ice","hot","dessert","sweet","cream"]},"doughnut":{"a":"Doughnut","b":"1F369","j":["food","breakfast","donut","dessert","sweet","snack"]},"cookie":{"a":"Cookie","b":"1F36A","j":["food","oreo","chocolate","dessert","sweet","snack"]},"birthday-cake":{"a":"Birthday Cake","b":"1F382","j":["food","birthday","celebration","pastry","cake","dessert","sweet"]},"shortcake":{"a":"Shortcake","b":"1F370","j":["food","pastry","cake","dessert","sweet","slice"]},"cupcake":{"a":"Cupcake","b":"1F9C1","j":["bakery","food","sweet","dessert"]},"pie":{"a":"Pie","b":"1F967","j":["food","fruit","pastry","filling","dessert","meat"]},"chocolate-bar":{"a":"Chocolate Bar","b":"1F36B","j":["food","chocolate","bar","dessert","sweet","snack"]},"candy":{"a":"Candy","b":"1F36C","j":["sweet","snack","lolly","dessert"]},"lollipop":{"a":"Lollipop","b":"1F36D","j":["candy","food","dessert","sweet","snack"]},"custard":{"a":"Custard","b":"1F36E","j":["sweet","food","dessert","pudding"]},"honey-pot":{"a":"Honey Pot","b":"1F36F","j":["honeypot","pot","kitchen","bees","honey","sweet"]},"baby-bottle":{"a":"Baby Bottle","b":"1F37C","j":["bottle","food","container","milk","drink","baby"]},"glass-of-milk":{"a":"Glass of Milk","b":"1F95B","j":["cow","milk","beverage","drink","glass"]},"hot-beverage":{"a":"Hot Beverage","b":"2615","j":["latte","tea","steaming","coffee","caffeine","hot","espresso","beverage","drink"]},"teapot":{"a":"Teapot","b":"1FAD6","j":["tea","hot","drink","pot"]},"teacup-without-handle":{"a":"Teacup Without Handle","b":"1F375","j":["tea","teacup","british","breakfast","green","beverage","drink","bowl","cup"]},"sake":{"a":"Sake","b":"1F376","j":["bottle","drunk","wine","booze","beverage","bar","drink","japanese","alcohol","cup"]},"bottle-with-popping-cork":{"a":"Bottle with Popping Cork","b":"1F37E","j":["bottle","wine","celebration","cork","bar","drink","popping"]},"wine-glass":{"a":"Wine Glass","b":"1F377","j":["wine","booze","beverage","bar","drink","drunk","alcohol","glass"]},"cocktail-glass":{"a":"Cocktail Glass","b":"1F378","j":["mojito","booze","beverage","bar","cocktail","drink","drunk","alcohol","glass"]},"tropical-drink":{"a":"Tropical Drink","b":"1F379","j":["mojito","booze","beverage","bar","drink","cocktail","beach","summer","alcohol","tropical"]},"beer-mug":{"a":"Beer Mug","b":"1F37A","j":["relax","beer","booze","beverage","bar","drink","summer","drunk","alcohol","pub","mug","party"]},"clinking-beer-mugs":{"a":"Clinking Beer Mugs","b":"1F37B","j":["relax","beer","booze","beverage","bar","drink","summer","drunk","alcohol","pub","mug","clink","party"]},"clinking-glasses":{"a":"Clinking Glasses","b":"1F942","j":["wine","champagne","cheers","toast","beverage","drink","celebrate","alcohol","glass","clink","party"]},"tumbler-glass":{"a":"Tumbler Glass","b":"1F943","j":["whisky","booze","liquor","bourbon","shot","beverage","drink","scotch","drunk","alcohol","glass","tumbler"]},"cup-with-straw":{"a":"Cup with Straw","b":"1F964","j":["soda","soft drink","water","juice","malt","drink"]},"bubble-tea":{"a":"Bubble Tea","b":"1F9CB","j":["tea","boba","milk tea","bubble","straw","milk","taiwan","pearl"]},"beverage-box":{"a":"Beverage Box","b":"1F9C3","j":["straw","juice","beverage","drink","box","sweet"]},"mate":{"a":"Mate","b":"1F9C9","j":["beverage","tea","drink"]},"ice":{"a":"Ice","b":"1F9CA","j":["cold","water","ice cube","iceberg"]},"chopsticks":{"a":"Chopsticks","b":"1F962","j":["jeotgarak","food","hashi","kuaizi"]},"fork-and-knife-with-plate":{"a":"Fork and Knife with Plate","b":"1F37D","j":["food","dinner","knife","cooking","restaurant","plate","meal","lunch","fork","eat"]},"fork-and-knife":{"a":"Fork and Knife","b":"1F374","j":["cooking","knife","kitchen","cutlery","fork"]},"spoon":{"a":"Spoon","b":"1F944","j":["tableware","kitchen","cutlery"]},"kitchen-knife":{"a":"Kitchen Knife","b":"1F52A","j":["blade","cooking","knife","hocho","tool","kitchen","cutlery","weapon"]},"amphora":{"a":"Amphora","b":"1F3FA","j":["jar","cooking","Aquarius","jug","drink","vase","zodiac"]},"globe-showing-europeafrica":{"a":"Globe Showing Europe-Africa","b":"1F30D","j":["Europe","globe showing Europe-Africa","world","international","Africa","globe_showing_europe_africa","earth","globe"]},"globe-showing-americas":{"a":"Globe Showing Americas","b":"1F30E","j":["USA","world","international","globe showing Americas","earth","Americas","globe"]},"globe-showing-asiaaustralia":{"a":"Globe Showing Asia-Australia","b":"1F30F","j":["globe showing Asia-Australia","world","international","Asia","earth","globe_showing_asia_australia","east","Australia","globe"]},"globe-with-meridians":{"a":"Globe with Meridians","b":"1F310","j":["world","international","meridians","internet","earth","interweb","globe","i18n"]},"world-map":{"a":"World Map","b":"1F5FA","j":["location","direction","map","world"]},"map-of-japan":{"a":"Map of Japan","b":"1F5FE","j":["map of Japan","Japan","asia","japanese","country","map","nation"]},"compass":{"a":"Compass","b":"1F9ED","j":["magnetic","orienteering","navigation"]},"snowcapped-mountain":{"a":"Snow-Capped Mountain","b":"1F3D4","j":["snow-capped mountain","snow_capped_mountain","snow","photo","winter","mountain","nature","cold","environment"]},"mountain":{"a":"Mountain","b":"26F0","j":["photo","environment","nature"]},"volcano":{"a":"Volcano","b":"1F30B","j":["photo","eruption","disaster","mountain","nature"]},"mount-fuji":{"a":"Mount Fuji","b":"1F5FB","j":["fuji","photo","mountain","nature","japanese"]},"camping":{"a":"Camping","b":"1F3D5","j":["photo","outdoors","tent"]},"beach-with-umbrella":{"a":"Beach with Umbrella","b":"1F3D6","j":["mojito","sand","umbrella","sunny","beach","summer","weather"]},"desert":{"a":"Desert","b":"1F3DC","j":["photo","saharah","warm"]},"desert-island":{"a":"Desert Island","b":"1F3DD","j":["mojito","photo","desert","island","tropical"]},"national-park":{"a":"National Park","b":"1F3DE","j":["photo","park","environment","nature"]},"stadium":{"a":"Stadium","b":"1F3DF","j":["sports","photo","concert","venue","place"]},"classical-building":{"a":"Classical Building","b":"1F3DB","j":["history","classical","art","culture"]},"building-construction":{"a":"Building Construction","b":"1F3D7","j":["working","construction","progress","wip"]},"brick":{"a":"Brick","b":"1F9F1","j":["clay","bricks","mortar","wall"]},"rock":{"a":"Rock","b":"1FAA8","j":["heavy","solid","boulder","stone"]},"wood":{"a":"Wood","b":"1FAB5","j":["log","trunk","timber","nature","lumber"]},"hut":{"a":"Hut","b":"1F6D6","j":["yurt","roundhouse","structure","house"]},"houses":{"a":"Houses","b":"1F3D8","j":["photo","buildings"]},"derelict-house":{"a":"Derelict House","b":"1F3DA","j":["evict","house","broken","derelict","building","abandon"]},"house":{"a":"House","b":"1F3E0","j":["building","home"]},"house-with-garden":{"a":"House with Garden","b":"1F3E1","j":["nature","house","home","plant","garden"]},"office-building":{"a":"Office Building","b":"1F3E2","j":["building","work","bureau"]},"japanese-post-office":{"a":"Japanese Post Office","b":"1F3E3","j":["communication","envelope","Japanese post office","Japanese","building","post"]},"post-office":{"a":"Post Office","b":"1F3E4","j":["email","building","European","post"]},"hospital":{"a":"Hospital","b":"1F3E5","j":["health","medicine","surgery","doctor","building"]},"bank":{"a":"Bank","b":"1F3E6","j":["business","enterprise","cash","money","building","sales"]},"hotel":{"a":"Hotel","b":"1F3E8","j":["accomodation","building","checkin"]},"love-hotel":{"a":"Love Hotel","b":"1F3E9","j":["love","like","dating","hotel","affection"]},"convenience-store":{"a":"Convenience Store","b":"1F3EA","j":["store","building","groceries","convenience","shopping"]},"school":{"a":"School","b":"1F3EB","j":["student","learn","teach","education","building"]},"department-store":{"a":"Department Store","b":"1F3EC","j":["store","building","department","mall","shopping"]},"factory":{"a":"Factory","b":"1F3ED","j":["smoke","building","pollution","industry"]},"japanese-castle":{"a":"Japanese Castle","b":"1F3EF","j":["castle","building","Japanese","photo"]},"castle":{"a":"Castle","b":"1F3F0","j":["building","royalty","history","European"]},"wedding":{"a":"Wedding","b":"1F492","j":["romance","bride","marriage","love","couple","like","chapel","groom","affection"]},"tokyo-tower":{"a":"Tokyo Tower","b":"1F5FC","j":["photo","japanese","tower","Tokyo"]},"statue-of-liberty":{"a":"Statue of Liberty","b":"1F5FD","j":["statue","newyork","liberty","american"]},"church":{"a":"Church","b":"26EA","j":["religion","cross","building","Christian","christ"]},"mosque":{"a":"Mosque","b":"1F54C","j":["islam","worship","minaret","religion","Muslim"]},"hindu-temple":{"a":"Hindu Temple","b":"1F6D5","j":["temple","religion","hindu"]},"synagogue":{"a":"Synagogue","b":"1F54D","j":["Jew","worship","religion","jewish","temple","Jewish","judaism"]},"shinto-shrine":{"a":"Shinto Shrine","b":"26E9","j":["kyoto","shrine","religion","temple","japan","shinto"]},"kaaba":{"a":"Kaaba","b":"1F54B","j":["islam","religion","mosque","mecca","Muslim"]},"fountain":{"a":"Fountain","b":"26F2","j":["photo","water","fresh","summer"]},"tent":{"a":"Tent","b":"26FA","j":["photo","outdoors","camping"]},"foggy":{"a":"Foggy","b":"1F301","j":["photo","fog","mountain"]},"night-with-stars":{"a":"Night with Stars","b":"1F303","j":["night","downtown","city","evening","star"]},"cityscape":{"a":"Cityscape","b":"1F3D9","j":["photo","night life","urban","city"]},"sunrise-over-mountains":{"a":"Sunrise over Mountains","b":"1F304","j":["morning","photo","sunrise","sun","mountain","view","vacation"]},"sunrise":{"a":"Sunrise","b":"1F305","j":["morning","photo","sun","view","vacation"]},"cityscape-at-dusk":{"a":"Cityscape at Dusk","b":"1F306","j":["dusk","photo","sunset","city","sky","evening","landscape","buildings"]},"sunset":{"a":"Sunset","b":"1F307","j":["dusk","photo","good morning","dawn","sun"]},"bridge-at-night":{"a":"Bridge at Night","b":"1F309","j":["photo","night","bridge","sanfrancisco"]},"hot-springs":{"a":"Hot Springs","b":"2668","j":["relax","steaming","bath","springs","hot","warm","hotsprings"]},"carousel-horse":{"a":"Carousel Horse","b":"1F3A0","j":["photo","carnival","carousel","horse"]},"ferris-wheel":{"a":"Ferris Wheel","b":"1F3A1","j":["photo","wheel","londoneye","amusement park","ferris","carnival"]},"roller-coaster":{"a":"Roller Coaster","b":"1F3A2","j":["roller","photo","playground","amusement park","carnival","coaster","fun"]},"barber-pole":{"a":"Barber Pole","b":"1F488","j":["pole","style","haircut","barber","hair","salon"]},"circus-tent":{"a":"Circus Tent","b":"1F3AA","j":["festival","circus","carnival","party","tent"]},"locomotive":{"a":"Locomotive","b":"1F682","j":["vehicle","train","transportation","steam","railway","engine"]},"railway-car":{"a":"Railway Car","b":"1F683","j":["car","electric","vehicle","train","trolleybus","transportation","railway","tram"]},"highspeed-train":{"a":"High-Speed Train","b":"1F684","j":["vehicle","high_speed_train","train","speed","transportation","shinkansen","railway","high-speed train"]},"bullet-train":{"a":"Bullet Train","b":"1F685","j":["vehicle","train","speed","transportation","bullet","shinkansen","railway","travel","fast","public"]},"train":{"a":"Train","b":"1F686","j":["vehicle","railway","transportation"]},"metro":{"a":"Metro","b":"1F687","j":["transportation","subway","tube","blue-square","mrt","underground"]},"light-rail":{"a":"Light Rail","b":"1F688","j":["vehicle","railway","transportation"]},"station":{"a":"Station","b":"1F689","j":["vehicle","train","transportation","railway","public"]},"tram":{"a":"Tram","b":"1F68A","j":["vehicle","trolleybus","transportation"]},"monorail":{"a":"Monorail","b":"1F69D","j":["vehicle","transportation"]},"mountain-railway":{"a":"Mountain Railway","b":"1F69E","j":["car","vehicle","transportation","railway","mountain"]},"tram-car":{"a":"Tram Car","b":"1F68B","j":["car","vehicle","trolleybus","transportation","carriage","travel","public","tram"]},"bus":{"a":"Bus","b":"1F68C","j":["car","vehicle","transportation"]},"oncoming-bus":{"a":"Oncoming Bus","b":"1F68D","j":["bus","vehicle","transportation","oncoming"]},"trolleybus":{"a":"Trolleybus","b":"1F68E","j":["bus","vehicle","transportation","trolley","bart","tram"]},"minibus":{"a":"Minibus","b":"1F690","j":["bus","vehicle","transportation","car"]},"ambulance":{"a":"Ambulance","b":"1F691","j":["911","vehicle","health","hospital"]},"fire-engine":{"a":"Fire Engine","b":"1F692","j":["cars","truck","vehicle","transportation","fire","engine"]},"police-car":{"a":"Police Car","b":"1F693","j":["cars","car","vehicle","transportation","patrol","police","enforcement","legal","law"]},"oncoming-police-car":{"a":"Oncoming Police Car","b":"1F694","j":["car","vehicle","oncoming","911","police","enforcement","legal","law"]},"taxi":{"a":"Taxi","b":"1F695","j":["cars","vehicle","uber","transportation"]},"oncoming-taxi":{"a":"Oncoming Taxi","b":"1F696","j":["cars","vehicle","oncoming","taxi","uber"]},"automobile":{"a":"Automobile","b":"1F697","j":["car","vehicle","red","transportation"]},"oncoming-automobile":{"a":"Oncoming Automobile","b":"1F698","j":["car","vehicle","automobile","transportation","oncoming"]},"sport-utility-vehicle":{"a":"Sport Utility Vehicle","b":"1F699","j":["vehicle","recreational","transportation","sport utility"]},"pickup-truck":{"a":"Pickup Truck","b":"1F6FB","j":["car","truck","transportation","pickup","pick-up"]},"delivery-truck":{"a":"Delivery Truck","b":"1F69A","j":["cars","truck","transportation","delivery"]},"articulated-lorry":{"a":"Articulated Lorry","b":"1F69B","j":["cars","truck","vehicle","transportation","semi","lorry","express"]},"tractor":{"a":"Tractor","b":"1F69C","j":["car","vehicle","farming","agriculture"]},"racing-car":{"a":"Racing Car","b":"1F3CE","j":["car","sports","racing","race","f1","fast","formula"]},"motorcycle":{"a":"Motorcycle","b":"1F3CD","j":["racing","fast","sports","race"]},"motor-scooter":{"a":"Motor Scooter","b":"1F6F5","j":["vehicle","vespa","sasha","motor","scooter"]},"manual-wheelchair":{"a":"Manual Wheelchair","b":"1F9BD","j":["accessibility"]},"motorized-wheelchair":{"a":"Motorized Wheelchair","b":"1F9BC","j":["accessibility"]},"auto-rickshaw":{"a":"Auto Rickshaw","b":"1F6FA","j":["move","tuk tuk","transportation"]},"bicycle":{"a":"Bicycle","b":"1F6B2","j":["exercise","bike","sports","hipster"]},"kick-scooter":{"a":"Kick Scooter","b":"1F6F4","j":["vehicle","razor","scooter","kick"]},"skateboard":{"a":"Skateboard","b":"1F6F9","j":["board"]},"roller-skate":{"a":"Roller Skate","b":"1F6FC","j":["roller","skate","sports","footwear"]},"bus-stop":{"a":"Bus Stop","b":"1F68F","j":["bus","stop","busstop","transportation","wait"]},"motorway":{"a":"Motorway","b":"1F6E3","j":["highway","road","cupertino","interstate"]},"railway-track":{"a":"Railway Track","b":"1F6E4","j":["railway","train","transportation"]},"oil-drum":{"a":"Oil Drum","b":"1F6E2","j":["oil","barrell","drum"]},"fuel-pump":{"a":"Fuel Pump","b":"26FD","j":["gas","fuel","diesel","pump","fuelpump","station","petroleum","gas station"]},"police-car-light":{"a":"Police Car Light","b":"1F6A8","j":["car","ambulance","911","beacon","light","error","emergency","pinged","police","revolving","legal","law","alert"]},"horizontal-traffic-light":{"a":"Horizontal Traffic Light","b":"1F6A5","j":["light","traffic","signal","transportation"]},"vertical-traffic-light":{"a":"Vertical Traffic Light","b":"1F6A6","j":["traffic","transportation","driving","light","signal"]},"stop-sign":{"a":"Stop Sign","b":"1F6D1","j":["octagonal","stop","sign"]},"construction":{"a":"Construction","b":"1F6A7","j":["progress","caution","wip","barrier","warning"]},"anchor":{"a":"Anchor","b":"2693","j":["boat","ship","sea","tool","ferry"]},"sailboat":{"a":"Sailboat","b":"26F5","j":["boat","yacht","transportation","sea","ship","resort","water","summer","sailing"]},"canoe":{"a":"Canoe","b":"1F6F6","j":["ship","water","boat","paddle"]},"speedboat":{"a":"Speedboat","b":"1F6A4","j":["vehicle","boat","transportation","ship","summer"]},"passenger-ship":{"a":"Passenger Ship","b":"1F6F3","j":["yacht","ship","cruise","ferry","passenger"]},"ferry":{"a":"Ferry","b":"26F4","j":["ship","yacht","boat","passenger"]},"motor-boat":{"a":"Motor Boat","b":"1F6E5","j":["motorboat","ship","boat"]},"ship":{"a":"Ship","b":"1F6A2","j":["boat","transportation","titanic","deploy","passenger"]},"airplane":{"a":"Airplane","b":"2708","j":["vehicle","transportation","flight","aeroplane","fly"]},"small-airplane":{"a":"Small Airplane","b":"1F6E9","j":["vehicle","transportation","flight","aeroplane","fly","airplane"]},"airplane-departure":{"a":"Airplane Departure","b":"1F6EB","j":["check-in","departures","departure","flight","airport","aeroplane","airplane","landing"]},"airplane-arrival":{"a":"Airplane Arrival","b":"1F6EC","j":["flight","airport","arrivals","aeroplane","boarding","arriving","airplane","landing"]},"parachute":{"a":"Parachute","b":"1FA82","j":["hang-glide","parasail","skydive","fly","glide"]},"seat":{"a":"Seat","b":"1F4BA","j":["bus","chair","flight","transport","sit","fly","airplane"]},"helicopter":{"a":"Helicopter","b":"1F681","j":["vehicle","transportation","fly"]},"suspension-railway":{"a":"Suspension Railway","b":"1F69F","j":["vehicle","railway","suspension","transportation"]},"mountain-cableway":{"a":"Mountain Cableway","b":"1F6A0","j":["vehicle","transportation","gondola","cable","mountain","ski"]},"aerial-tramway":{"a":"Aerial Tramway","b":"1F6A1","j":["car","vehicle","transportation","gondola","cable","aerial","ski","tramway"]},"satellite":{"a":"Satellite","b":"1F6F0","j":["communication","gps","spaceflight","ISS","NASA","space","orbit"]},"rocket":{"a":"Rocket","b":"1F680","j":["staffmode","outer_space","NASA","ship","space","fly","outer space","launch"]},"flying-saucer":{"a":"Flying Saucer","b":"1F6F8","j":["vehicle","ufo","transportation","UFO"]},"bellhop-bell":{"a":"Bellhop Bell","b":"1F6CE","j":["bell","bellhop","hotel","service"]},"luggage":{"a":"Luggage","b":"1F9F3","j":["travel","packing"]},"hourglass-done":{"a":"Hourglass Done","b":"231B","j":["test","clock","exam","sand","limit","time","quiz","timer","oldschool"]},"hourglass-not-done":{"a":"Hourglass Not Done","b":"23F3","j":["sand","hourglass","time","countdown","timer","oldschool"]},"watch":{"a":"Watch","b":"231A","j":["time","clock","accessories"]},"alarm-clock":{"a":"Alarm Clock","b":"23F0","j":["time","clock","alarm","wake"]},"stopwatch":{"a":"Stopwatch","b":"23F1","j":["time","clock","deadline"]},"timer-clock":{"a":"Timer Clock","b":"23F2","j":["clock","timer","alarm"]},"mantelpiece-clock":{"a":"Mantelpiece Clock","b":"1F570","j":["time","clock"]},"twelve-oclock":{"a":"Twelve O’Clock","b":"1F55B","j":["o’clock","clock","twelve_o_clock","noon","12","00","late","time","early","twelve","midnight","12:00","midday","schedule"]},"twelvethirty":{"a":"Twelve-Thirty","b":"1F567","j":["clock","late","12","time","schedule","early","twelve_thirty","twelve","twelve-thirty","thirty","12:30"]},"one-oclock":{"a":"One O’Clock","b":"1F550","j":["o’clock","clock","late","00","time","early","one","1","1:00","one_o_clock","schedule"]},"onethirty":{"a":"One-Thirty","b":"1F55C","j":["clock","late","time","schedule","early","one","1","1:30","one_thirty","thirty","one-thirty"]},"two-oclock":{"a":"Two O’Clock","b":"1F551","j":["o’clock","clock","late","00","time","early","two","two_o_clock","2:00","2","schedule"]},"twothirty":{"a":"Two-Thirty","b":"1F55D","j":["2:30","clock","late","two_thirty","time","early","two","two-thirty","2","thirty","schedule"]},"three-oclock":{"a":"Three O’Clock","b":"1F552","j":["o’clock","clock","late","00","three","3","time","early","3:00","three_o_clock","schedule"]},"threethirty":{"a":"Three-Thirty","b":"1F55E","j":["clock","late","three","3","time","early","three_thirty","schedule","three-thirty","thirty","3:30"]},"four-oclock":{"a":"Four O’Clock","b":"1F553","j":["o’clock","clock","four","00","late","time","early","four_o_clock","4","4:00","schedule"]},"fourthirty":{"a":"Four-Thirty","b":"1F55F","j":["clock","four","four-thirty","late","time","4:30","early","four_thirty","4","thirty","schedule"]},"five-oclock":{"a":"Five O’Clock","b":"1F554","j":["o’clock","clock","5:00","00","late","five_o_clock","five","time","early","5","schedule"]},"fivethirty":{"a":"Five-Thirty","b":"1F560","j":["clock","5:30","late","five","time","early","five_thirty","5","five-thirty","thirty","schedule"]},"six-oclock":{"a":"Six O’Clock","b":"1F555","j":["o’clock","clock","late","00","dusk","time","six_o_clock","six","early","dawn","6","6:00","schedule"]},"sixthirty":{"a":"Six-Thirty","b":"1F561","j":["clock","late","six_thirty","time","six","early","6","six-thirty","6:30","thirty","schedule"]},"seven-oclock":{"a":"Seven O’Clock","b":"1F556","j":["o’clock","clock","seven","late","00","7","time","early","seven_o_clock","7:00","schedule"]},"seventhirty":{"a":"Seven-Thirty","b":"1F562","j":["seven_thirty","clock","seven","late","7","time","7:30","early","seven-thirty","thirty","schedule"]},"eight-oclock":{"a":"Eight O’Clock","b":"1F557","j":["o’clock","clock","late","00","8:00","time","early","eight","8","eight_o_clock","schedule"]},"eightthirty":{"a":"Eight-Thirty","b":"1F563","j":["clock","late","eight_thirty","time","8:30","early","eight-thirty","eight","8","thirty","schedule"]},"nine-oclock":{"a":"Nine O’Clock","b":"1F558","j":["o’clock","clock","late","00","nine","nine_o_clock","time","early","9:00","9","schedule"]},"ninethirty":{"a":"Nine-Thirty","b":"1F564","j":["clock","late","nine","time","early","nine-thirty","nine_thirty","9:30","9","thirty","schedule"]},"ten-oclock":{"a":"Ten O’Clock","b":"1F559","j":["o’clock","clock","10","late","00","ten_o_clock","time","early","ten","10:00","schedule"]},"tenthirty":{"a":"Ten-Thirty","b":"1F565","j":["ten-thirty","clock","10","late","time","ten_thirty","early","10:30","ten","thirty","schedule"]},"eleven-oclock":{"a":"Eleven O’Clock","b":"1F55A","j":["o’clock","clock","late","00","time","eleven","11","early","11:00","eleven_o_clock","schedule"]},"eleventhirty":{"a":"Eleven-Thirty","b":"1F566","j":["clock","late","eleven-thirty","time","schedule","eleven","11","early","eleven_thirty","thirty","11:30"]},"new-moon":{"a":"New Moon","b":"1F311","j":["planet","night","space","evening","twilight","nature","dark","moon","sleep"]},"waxing-crescent-moon":{"a":"Waxing Crescent Moon","b":"1F312","j":["planet","night","waxing","space","evening","crescent","twilight","nature","moon","sleep"]},"first-quarter-moon":{"a":"First Quarter Moon","b":"1F313","j":["quarter","planet","night","space","evening","twilight","nature","moon","sleep"]},"waxing-gibbous-moon":{"a":"Waxing Gibbous Moon","b":"1F314","j":["planet","night","waxing","gibbous","gray","space","sky","evening","twilight","nature","moon","sleep"]},"full-moon":{"a":"Full Moon","b":"1F315","j":["yellow","planet","night","full","space","evening","twilight","nature","moon","sleep"]},"waning-gibbous-moon":{"a":"Waning Gibbous Moon","b":"1F316","j":["waning","planet","night","gibbous","space","waxing_gibbous_moon","evening","twilight","nature","moon","sleep"]},"last-quarter-moon":{"a":"Last Quarter Moon","b":"1F317","j":["quarter","planet","night","space","evening","twilight","nature","moon","sleep"]},"waning-crescent-moon":{"a":"Waning Crescent Moon","b":"1F318","j":["waning","planet","night","space","evening","crescent","twilight","nature","moon","sleep"]},"crescent-moon":{"a":"Crescent Moon","b":"1F319","j":["night","magic","sky","evening","crescent","moon","sleep"]},"new-moon-face":{"a":"New Moon Face","b":"1F31A","j":["planet","night","space","evening","twilight","nature","moon","face","sleep"]},"first-quarter-moon-face":{"a":"First Quarter Moon Face","b":"1F31B","j":["quarter","planet","night","space","evening","twilight","nature","moon","face","sleep"]},"last-quarter-moon-face":{"a":"Last Quarter Moon Face","b":"1F31C","j":["quarter","planet","night","space","evening","twilight","nature","moon","face","sleep"]},"thermometer":{"a":"Thermometer","b":"1F321","j":["cold","hot","temperature","weather"]},"sun":{"a":"Sun","b":"2600","j":["rays","spring","sunny","bright","nature","summer","beach","brightness","weather"]},"full-moon-face":{"a":"Full Moon Face","b":"1F31D","j":["planet","night","full","space","evening","bright","twilight","nature","moon","face","sleep"]},"sun-with-face":{"a":"Sun with Face","b":"1F31E","j":["morning","sun","sky","bright","nature","face"]},"ringed-planet":{"a":"Ringed Planet","b":"1FA90","j":["saturn","outerspace","saturnine"]},"star":{"a":"Star","b":"2B50","j":["yellow","night"]},"glowing-star":{"a":"Glowing Star","b":"1F31F","j":["sparkle","night","glow","awesome","glittery","magic","good","star","shining"]},"shooting-star":{"a":"Shooting Star","b":"1F320","j":["night","shooting","photo","falling","star"]},"milky-way":{"a":"Milky Way","b":"1F30C","j":["photo","space","stars"]},"cloud":{"a":"Cloud","b":"2601","j":["sky","weather"]},"sun-behind-cloud":{"a":"Sun Behind Cloud","b":"26C5","j":["morning","cloud","spring","cloudy","sun","nature","fall","weather"]},"cloud-with-lightning-and-rain":{"a":"Cloud with Lightning and Rain","b":"26C8","j":["rain","lightning","thunder","cloud","weather"]},"sun-behind-small-cloud":{"a":"Sun Behind Small Cloud","b":"1F324","j":["sun","cloud","weather"]},"sun-behind-large-cloud":{"a":"Sun Behind Large Cloud","b":"1F325","j":["sun","cloud","weather"]},"sun-behind-rain-cloud":{"a":"Sun Behind Rain Cloud","b":"1F326","j":["rain","sun","cloud","weather"]},"cloud-with-rain":{"a":"Cloud with Rain","b":"1F327","j":["rain","cloud","weather"]},"cloud-with-snow":{"a":"Cloud with Snow","b":"1F328","j":["cold","cloud","snow","weather"]},"cloud-with-lightning":{"a":"Cloud with Lightning","b":"1F329","j":["lightning","cloud","thunder","weather"]},"tornado":{"a":"Tornado","b":"1F32A","j":["whirlwind","twister","cyclone","cloud","weather"]},"fog":{"a":"Fog","b":"1F32B","j":["cloud","weather"]},"wind-face":{"a":"Wind Face","b":"1F32C","j":["air","gust","cloud","wind","blow","face"]},"cyclone":{"a":"Cyclone","b":"1F300","j":["vortex","dizzy","tornado","twister","cloud","hurricane","typhoon","spin","weather","swirl","whirlpool","spiral","blue"]},"rainbow":{"a":"Rainbow","b":"1F308","j":["rain","photo","spring","sky","nature","unicorn_face","happy"]},"closed-umbrella":{"a":"Closed Umbrella","b":"1F302","j":["clothing","drizzle","umbrella","rain","weather"]},"umbrella":{"a":"Umbrella","b":"2602","j":["rain","clothing","spring","weather"]},"umbrella-with-rain-drops":{"a":"Umbrella with Rain Drops","b":"2614","j":["clothing","umbrella","drop","rain","spring","rainy","weather"]},"umbrella-on-ground":{"a":"Umbrella on Ground","b":"26F1","j":["umbrella","rain","sun","summer","weather"]},"high-voltage":{"a":"High Voltage","b":"26A1","j":["electric","zap","lightning","thunder","lightning bolt","fast","voltage","danger","weather"]},"snowflake":{"a":"Snowflake","b":"2744","j":["xmas","snow","christmas","season","winter","cold","weather"]},"snowman":{"a":"Snowman","b":"2603","j":["xmas","snow","christmas","season","winter","weather","cold","frozen"]},"snowman-without-snow":{"a":"Snowman Without Snow","b":"26C4","j":["xmas","snow","christmas","winter","season","frozen","cold","without_snow","snowman","weather"]},"comet":{"a":"Comet","b":"2604","j":["space"]},"fire":{"a":"Fire","b":"1F525","j":["cook","hot","tool","flame"]},"droplet":{"a":"Droplet","b":"1F4A7","j":["sweat","drop","water","spring","drip","comic","cold","faucet"]},"water-wave":{"a":"Water Wave","b":"1F30A","j":["sea","water","ocean","tsunami","disaster","nature","wave"]},"jackolantern":{"a":"Jack-O-Lantern","b":"1F383","j":["jack_o_lantern","pumpkin","jack-o-lantern","celebration","halloween","creepy","light","jack","fall","lantern"]},"christmas-tree":{"a":"Christmas Tree","b":"1F384","j":["xmas","festival","celebration","Christmas","december","tree","vacation"]},"fireworks":{"a":"Fireworks","b":"1F386","j":["festival","photo","celebration","carnival","congratulations"]},"sparkler":{"a":"Sparkler","b":"1F387","j":["stars","night","sparkle","fireworks","celebration","shine"]},"firecracker":{"a":"Firecracker","b":"1F9E8","j":["explode","explosive","fireworks","boom","dynamite","explosion"]},"sparkles":{"a":"Sparkles","b":"2728","j":["*","stars","sparkle","cool","awesome","magic","shiny","good","shine","star"]},"balloon":{"a":"Balloon","b":"1F388","j":["birthday","celebration","circus","party"]},"party-popper":{"a":"Party Popper","b":"1F389","j":["birthday","celebration","tada","magic","popper","congratulations","circus","party"]},"confetti-ball":{"a":"Confetti Ball","b":"1F38A","j":["festival","birthday","celebration","ball","confetti","circus","party"]},"tanabata-tree":{"a":"Tanabata Tree","b":"1F38B","j":["branch","celebration","Japanese","banner","tree","nature","summer","plant"]},"pine-decoration":{"a":"Pine Decoration","b":"1F38D","j":["pine","panda","vegetable","bamboo","celebration","Japanese","nature","plant"]},"japanese-dolls":{"a":"Japanese Dolls","b":"1F38E","j":["Japanese dolls","festival","toy","celebration","Japanese","kimono","japanese","doll"]},"carp-streamer":{"a":"Carp Streamer","b":"1F38F","j":["fish","celebration","carp","banner","streamer","japanese","koinobori"]},"wind-chime":{"a":"Wind Chime","b":"1F390","j":["spring","celebration","ding","chime","bell","wind","nature"]},"moon-viewing-ceremony":{"a":"Moon Viewing Ceremony","b":"1F391","j":["ceremony","photo","celebration","tsukimi","asia","japan","moon"]},"red-envelope":{"a":"Red Envelope","b":"1F9E7","j":["good luck","hóngbāo","lai see","gift","money"]},"ribbon":{"a":"Ribbon","b":"1F380","j":["pink","decoration","celebration","girl","bowtie"]},"wrapped-gift":{"a":"Wrapped Gift","b":"1F381","j":["xmas","wrapped","christmas","birthday","celebration","present","gift","box"]},"reminder-ribbon":{"a":"Reminder Ribbon","b":"1F397","j":["cause","ribbon","sports","support","celebration","reminder","awareness"]},"admission-tickets":{"a":"Admission Tickets","b":"1F39F","j":["entrance","sports","admission","concert","ticket"]},"ticket":{"a":"Ticket","b":"1F3AB","j":["concert","admission","pass","event"]},"military-medal":{"a":"Military Medal","b":"1F396","j":["winning","military","award","celebration","army","medal"]},"trophy":{"a":"Trophy","b":"1F3C6","j":["ftw","win","prize","award","ceremony","contest","place"]},"sports-medal":{"a":"Sports Medal","b":"1F3C5","j":["medal","winning","award"]},"1st-place-medal":{"a":"1st Place Medal","b":"1F947","j":["winning","gold","award","first","medal"]},"2nd-place-medal":{"a":"2nd Place Medal","b":"1F948","j":["medal","second","silver","award"]},"3rd-place-medal":{"a":"3rd Place Medal","b":"1F949","j":["medal","third","bronze","award"]},"soccer-ball":{"a":"Soccer Ball","b":"26BD","j":["football","soccer","sports","ball"]},"baseball":{"a":"Baseball","b":"26BE","j":["balls","sports","ball"]},"softball":{"a":"Softball","b":"1F94E","j":["sports","balls","underarm","glove","ball"]},"basketball":{"a":"Basketball","b":"1F3C0","j":["sports","balls","ball","NBA","hoop"]},"volleyball":{"a":"Volleyball","b":"1F3D0","j":["game","balls","sports","ball"]},"american-football":{"a":"American Football","b":"1F3C8","j":["NFL","sports","balls","ball","football","american"]},"rugby-football":{"a":"Rugby Football","b":"1F3C9","j":["sports","team","rugby","ball","football"]},"tennis":{"a":"Tennis","b":"1F3BE","j":["sports","balls","green","racquet","ball"]},"flying-disc":{"a":"Flying Disc","b":"1F94F","j":["ultimate","sports","frisbee"]},"bowling":{"a":"Bowling","b":"1F3B3","j":["sports","ball","game","play","fun"]},"cricket-game":{"a":"Cricket Game","b":"1F3CF","j":["game","bat","sports","ball"]},"field-hockey":{"a":"Field Hockey","b":"1F3D1","j":["sports","hockey","ball","stick","game","field"]},"ice-hockey":{"a":"Ice Hockey","b":"1F3D2","j":["ice","sports","hockey","puck","stick","game"]},"lacrosse":{"a":"Lacrosse","b":"1F94D","j":["stick","sports","goal","ball"]},"ping-pong":{"a":"Ping Pong","b":"1F3D3","j":["bat","pingpong","sports","paddle","table tennis","ball","game"]},"badminton":{"a":"Badminton","b":"1F3F8","j":["birdie","sports","racquet","game","shuttlecock"]},"boxing-glove":{"a":"Boxing Glove","b":"1F94A","j":["boxing","glove","sports","fighting"]},"martial-arts-uniform":{"a":"Martial Arts Uniform","b":"1F94B","j":["taekwondo","karate","uniform","martial arts","judo"]},"goal-net":{"a":"Goal Net","b":"1F945","j":["net","sports","goal"]},"flag-in-hole":{"a":"Flag in Hole","b":"26F3","j":["business","hole","sports","golf","flag","summer"]},"ice-skate":{"a":"Ice Skate","b":"26F8","j":["skate","sports","ice"]},"fishing-pole":{"a":"Fishing Pole","b":"1F3A3","j":["pole","fish","food","hobby","summer"]},"diving-mask":{"a":"Diving Mask","b":"1F93F","j":["scuba","sport","ocean","diving","snorkeling"]},"running-shirt":{"a":"Running Shirt","b":"1F3BD","j":["running","sash","pageant","shirt","athletics","play"]},"skis":{"a":"Skis","b":"1F3BF","j":["sports","snow","winter","cold","ski"]},"sled":{"a":"Sled","b":"1F6F7","j":["luge","sledge","sleigh","toboggan"]},"curling-stone":{"a":"Curling Stone","b":"1F94C","j":["game","sports","rock"]},"bullseye":{"a":"Bullseye","b":"1F3AF","j":["dart","direct hit","target","bar","hit","game","direct_hit","play"]},"yoyo":{"a":"Yo-Yo","b":"1FA80","j":["toy","yo-yo","fluctuate","yo_yo"]},"kite":{"a":"Kite","b":"1FA81","j":["wind","soar","fly"]},"pool-8-ball":{"a":"Pool 8 Ball","b":"1F3B1","j":["8","billiard","pool","hobby","luck","magic","eight","ball","game"]},"crystal-ball":{"a":"Crystal Ball","b":"1F52E","j":["fairy tale","tool","fantasy","magic","crystal","fortune_teller","ball","fortune","disco","circus","party"]},"magic-wand":{"a":"Magic Wand","b":"1FA84","j":["wizard","witch","magic","supernature","power"]},"nazar-amulet":{"a":"Nazar Amulet","b":"1F9FF","j":["nazar","talisman","evil-eye","bead","charm"]},"video-game":{"a":"Video Game","b":"1F3AE","j":["console","PS4","controller","game","play"]},"joystick":{"a":"Joystick","b":"1F579","j":["game","video game","play"]},"slot-machine":{"a":"Slot Machine","b":"1F3B0","j":["casino","bet","vegas","luck","slot","gamble","game","fruit machine"]},"game-die":{"a":"Game Die","b":"1F3B2","j":["random","tabletop","luck","die","game","dice","play"]},"puzzle-piece":{"a":"Puzzle Piece","b":"1F9E9","j":["puzzle","piece","jigsaw","interlocking","clue"]},"teddy-bear":{"a":"Teddy Bear","b":"1F9F8","j":["toy","plaything","plush","stuffed"]},"piata":{"a":"Piñata","b":"1FA85","j":["candy","celebration","mexico","pinata","piñata","party"]},"nesting-dolls":{"a":"Nesting Dolls","b":"1FA86","j":["matryoshka","toy","nesting","doll","russia"]},"spade-suit":{"a":"Spade Suit","b":"2660","j":["cards","suits","card","magic","game","poker"]},"heart-suit":{"a":"Heart Suit","b":"2665","j":["cards","suits","card","magic","game","poker"]},"diamond-suit":{"a":"Diamond Suit","b":"2666","j":["cards","suits","card","magic","game","poker"]},"club-suit":{"a":"Club Suit","b":"2663","j":["cards","suits","card","magic","game","poker"]},"chess-pawn":{"a":"Chess Pawn","b":"265F","j":["chess","dupe","expendable"]},"joker":{"a":"Joker","b":"1F0CF","j":["cards","card","magic","wildcard","game","poker","play"]},"mahjong-red-dragon":{"a":"Mahjong Red Dragon","b":"1F004","j":["red","mahjong","chinese","kanji","game","play"]},"flower-playing-cards":{"a":"Flower Playing Cards","b":"1F3B4","j":["card","red","sunset","Japanese","playing","flower","game"]},"performing-arts":{"a":"Performing Arts","b":"1F3AD","j":["theatre","drama","acting","mask","performing","art","theater"]},"framed-picture":{"a":"Framed Picture","b":"1F5BC","j":["frame","photography","museum","art","picture","painting"]},"artist-palette":{"a":"Artist Palette","b":"1F3A8","j":["draw","paint","museum","palette","design","colors","art","painting"]},"thread":{"a":"Thread","b":"1F9F5","j":["needle","spool","sewing","string"]},"sewing-needle":{"a":"Sewing Needle","b":"1FAA1","j":["needle","sewing","embroidery","sutures","stitches","tailoring"]},"yarn":{"a":"Yarn","b":"1F9F6","j":["knit","crochet","ball"]},"knot":{"a":"Knot","b":"1FAA2","j":["rope","twist","tangled","tie","twine","scout"]},"glasses":{"a":"Glasses","b":"1F453","j":["eyeglasses","nerdy","clothing","dork","eye","accessories","eyesight","fashion","geek","eyewear"]},"sunglasses":{"a":"Sunglasses","b":"1F576","j":["cool","glasses","eye","accessories","dark","eyewear","face"]},"goggles":{"a":"Goggles","b":"1F97D","j":["swimming","eyes","eye protection","welding","safety","protection"]},"lab-coat":{"a":"Lab Coat","b":"1F97C","j":["chemist","doctor","scientist","experiment"]},"safety-vest":{"a":"Safety Vest","b":"1F9BA","j":["vest","emergency","safety","protection"]},"necktie":{"a":"Necktie","b":"1F454","j":["business","clothing","suitup","shirt","tie","fashion","formal","cloth"]},"tshirt":{"a":"T-Shirt","b":"1F455","j":["casual","clothing","t_shirt","shirt","fashion","tee","t-shirt","cloth"]},"jeans":{"a":"Jeans","b":"1F456","j":["clothing","trousers","fashion","pants","shopping"]},"scarf":{"a":"Scarf","b":"1F9E3","j":["clothes","neck","winter"]},"gloves":{"a":"Gloves","b":"1F9E4","j":["clothes","hand","winter","hands"]},"coat":{"a":"Coat","b":"1F9E5","j":["jacket"]},"socks":{"a":"Socks","b":"1F9E6","j":["clothes","stockings","stocking"]},"dress":{"a":"Dress","b":"1F457","j":["clothes","clothing","fashion","shopping"]},"kimono":{"a":"Kimono","b":"1F458","j":["clothing","dress","female","fashion","japanese","women"]},"sari":{"a":"Sari","b":"1F97B","j":["clothing","dress"]},"onepiece-swimsuit":{"a":"One-Piece Swimsuit","b":"1FA71","j":["bathing suit","fashion","one-piece swimsuit","one_piece_swimsuit"]},"briefs":{"a":"Briefs","b":"1FA72","j":["clothing","swimsuit","one-piece","bathing suit","underwear"]},"shorts":{"a":"Shorts","b":"1FA73","j":["underwear","clothing","pants","bathing suit"]},"bikini":{"a":"Bikini","b":"1F459","j":["swim","clothing","woman","swimming","female","girl","fashion","beach","summer"]},"womans-clothes":{"a":"Woman’S Clothes","b":"1F45A","j":["clothing","shopping_bags","woman","woman_s_clothes","female","woman’s clothes","fashion"]},"purse":{"a":"Purse","b":"1F45B","j":["clothing","accessories","shopping","money","sales","fashion","coin"]},"handbag":{"a":"Handbag","b":"1F45C","j":["clothing","purse","accessory","accessories","fashion","bag","shopping"]},"clutch-bag":{"a":"Clutch Bag","b":"1F45D","j":["clothing","accessories","shopping","bag","pouch"]},"shopping-bags":{"a":"Shopping Bags","b":"1F6CD","j":["purchase","buy","bag","hotel","mall","shopping"]},"backpack":{"a":"Backpack","b":"1F392","j":["satchel","school","student","education","bag","rucksack"]},"thong-sandal":{"a":"Thong Sandal","b":"1FA74","j":["zōri","thongs","sandals","thong sandals","footwear","summer","beach sandals"]},"mans-shoe":{"a":"Man’S Shoe","b":"1F45E","j":["shoe","clothing","male","man","fashion","man_s_shoe","man’s shoe"]},"running-shoe":{"a":"Running Shoe","b":"1F45F","j":["shoe","clothing","sports","shoes","sneakers","athletic","sneaker"]},"hiking-boot":{"a":"Hiking Boot","b":"1F97E","j":["hiking","backpacking","camping","boot"]},"flat-shoe":{"a":"Flat Shoe","b":"1F97F","j":["slipper","slip-on","ballet flat","ballet"]},"highheeled-shoe":{"a":"High-Heeled Shoe","b":"1F460","j":["shoe","clothing","shoes","woman","stiletto","female","heel","pumps","fashion","high_heeled_shoe","high-heeled shoe"]},"womans-sandal":{"a":"Woman’S Sandal","b":"1F461","j":["shoe","flip flops","clothing","shoes","woman","woman_s_sandal","sandal","fashion","woman’s sandal"]},"ballet-shoes":{"a":"Ballet Shoes","b":"1FA70","j":["ballet","dance"]},"womans-boot":{"a":"Woman’S Boot","b":"1F462","j":["shoe","clothing","shoes","woman","woman’s boot","fashion","boot","woman_s_boot"]},"crown":{"a":"Crown","b":"1F451","j":["clothing","royalty","queen","leader","lord","kod","king"]},"womans-hat":{"a":"Woman’S Hat","b":"1F452","j":["clothing","hat","woman’s hat","woman","spring","woman_s_hat","accessories","female","fashion","lady"]},"top-hat":{"a":"Top Hat","b":"1F3A9","j":["clothing","hat","tophat","classy","top","magic","gentleman","circus"]},"graduation-cap":{"a":"Graduation Cap","b":"1F393","j":["clothing","hat","school","cap","celebration","degree","learn","education","university","graduation","legal","college"]},"billed-cap":{"a":"Billed Cap","b":"1F9E2","j":["cap","baseball","baseball cap"]},"military-helmet":{"a":"Military Helmet","b":"1FA96","j":["military","army","helmet","protection","warrior","soldier"]},"rescue-workers-helmet":{"a":"Rescue Worker’S Helmet","b":"26D1","j":["hat","rescue worker’s helmet","construction","build","cross","aid","helmet","rescue_worker_s_helmet","face"]},"prayer-beads":{"a":"Prayer Beads","b":"1F4FF","j":["beads","clothing","religion","religious","dhikr","necklace","prayer"]},"lipstick":{"a":"Lipstick","b":"1F484","j":["woman","female","makeup","girl","fashion","cosmetics"]},"ring":{"a":"Ring","b":"1F48D","j":["wedding","gem","marriage","jewelry","valentines","propose","fashion","diamond","engagement"]},"gem-stone":{"a":"Gem Stone","b":"1F48E","j":["gem","ruby","jewel","jewelry","diamond","blue"]},"muted-speaker":{"a":"Muted Speaker","b":"1F507","j":["speaker","sound","silent","mute","silence","volume","quiet"]},"speaker-low-volume":{"a":"Speaker Low Volume","b":"1F508","j":["broadcast","soft","silence","volume","sound"]},"speaker-medium-volume":{"a":"Speaker Medium Volume","b":"1F509","j":["speaker","volume","medium","broadcast"]},"speaker-high-volume":{"a":"Speaker High Volume","b":"1F50A","j":["speaker","broadcast","noise","loud","volume","noisy"]},"loudspeaker":{"a":"Loudspeaker","b":"1F4E2","j":["volume","public address","sound","loud"]},"megaphone":{"a":"Megaphone","b":"1F4E3","j":["speaker","cheering","sound","volume"]},"postal-horn":{"a":"Postal Horn","b":"1F4EF","j":["postal","horn","music","post","instrument"]},"bell":{"a":"Bell","b":"1F514","j":["xmas","christmas","chime","notification","sound"]},"bell-with-slash":{"a":"Bell with Slash","b":"1F515","j":["silent","mute","bell","volume","sound","forbidden","quiet"]},"musical-score":{"a":"Musical Score","b":"1F3BC","j":["score","treble","clef","compose","music"]},"musical-note":{"a":"Musical Note","b":"1F3B5","j":["score","note","music","tone","sound"]},"musical-notes":{"a":"Musical Notes","b":"1F3B6","j":["note","score","music","notes"]},"studio-microphone":{"a":"Studio Microphone","b":"1F399","j":["mic","recording","microphone","sing","talkshow","music","artist","studio"]},"level-slider":{"a":"Level Slider","b":"1F39A","j":["scale","slider","music","level"]},"control-knobs":{"a":"Control Knobs","b":"1F39B","j":["control","dial","music","knobs"]},"microphone":{"a":"Microphone","b":"1F3A4","j":["mic","karaoke","sing","PA","talkshow","music","sound"]},"headphone":{"a":"Headphone","b":"1F3A7","j":["gadgets","earbud","music","score"]},"radio":{"a":"Radio","b":"1F4FB","j":["podcast","communication","video","program","music"]},"saxophone":{"a":"Saxophone","b":"1F3B7","j":["sax","jazz","blues","music","instrument"]},"accordion":{"a":"Accordion","b":"1FA97","j":["concertina","music","squeeze box"]},"guitar":{"a":"Guitar","b":"1F3B8","j":["instrument","music"]},"musical-keyboard":{"a":"Musical Keyboard","b":"1F3B9","j":["piano","keyboard","compose","music","instrument"]},"trumpet":{"a":"Trumpet","b":"1F3BA","j":["brass","instrument","music"]},"violin":{"a":"Violin","b":"1F3BB","j":["orchestra","instrument","music","symphony"]},"banjo":{"a":"Banjo","b":"1FA95","j":["instructment","stringed","music"]},"drum":{"a":"Drum","b":"1F941","j":["drumsticks","instrument","music","snare"]},"long-drum":{"a":"Long Drum","b":"1FA98","j":["beat","rhythm","drum","music","conga"]},"mobile-phone":{"a":"Mobile Phone","b":"1F4F1","j":["gadgets","dial","mobile","cell","phone","telephone","technology","apple"]},"mobile-phone-with-arrow":{"a":"Mobile Phone with Arrow","b":"1F4F2","j":["mobile","receive","arrow","cell","incoming","iphone","phone"]},"telephone":{"a":"Telephone","b":"260E","j":["dial","communication","phone","technology"]},"telephone-receiver":{"a":"Telephone Receiver","b":"1F4DE","j":["communication","dial","receiver","telephone","technology","phone"]},"pager":{"a":"Pager","b":"1F4DF","j":["bbcall","90s","oldschool"]},"fax-machine":{"a":"Fax Machine","b":"1F4E0","j":["communication","fax","technology"]},"battery":{"a":"Battery","b":"1F50B","j":["sustain","energy","power"]},"electric-plug":{"a":"Electric Plug","b":"1F50C","j":["electric","electricity","plug","charger","power"]},"laptop":{"a":"Laptop","b":"1F4BB","j":["screen","personal","display","technology","computer","pc","monitor"]},"desktop-computer":{"a":"Desktop Computer","b":"1F5A5","j":["screen","desktop","technology","computer","computing"]},"printer":{"a":"Printer","b":"1F5A8","j":["paper","ink","computer"]},"keyboard":{"a":"Keyboard","b":"2328","j":["type","text","technology","computer","input"]},"computer-mouse":{"a":"Computer Mouse","b":"1F5B1","j":["click","computer"]},"trackball":{"a":"Trackball","b":"1F5B2","j":["trackpad","technology","computer"]},"computer-disk":{"a":"Computer Disk","b":"1F4BD","j":["90s","optical","minidisk","technology","computer","record","data","disk"]},"floppy-disk":{"a":"Floppy Disk","b":"1F4BE","j":["oldschool","80s","90s","floppy","technology","computer","save","disk"]},"optical-disk":{"a":"Optical Disk","b":"1F4BF","j":["90s","disc","optical","cd","technology","computer","dvd","disk"]},"dvd":{"a":"Dvd","b":"1F4C0","j":["blu-ray","disc","optical","cd","computer","disk"]},"abacus":{"a":"Abacus","b":"1F9EE","j":["calculation"]},"movie-camera":{"a":"Movie Camera","b":"1F3A5","j":["movie","camera","cinema","film","record"]},"film-frames":{"a":"Film Frames","b":"1F39E","j":["cinema","frames","movie","film"]},"film-projector":{"a":"Film Projector","b":"1F4FD","j":["movie","cinema","projector","video","film","record","tape"]},"clapper-board":{"a":"Clapper Board","b":"1F3AC","j":["clapper","movie","record","film"]},"television":{"a":"Television","b":"1F4FA","j":["video","program","tv","technology","oldschool","show"]},"camera":{"a":"Camera","b":"1F4F7","j":["gadgets","video","photography"]},"camera-with-flash":{"a":"Camera with Flash","b":"1F4F8","j":["gadgets","camera","photography","flash","video"]},"video-camera":{"a":"Video Camera","b":"1F4F9","j":["record","camera","video","film"]},"videocassette":{"a":"Videocassette","b":"1F4FC","j":["vhs","80s","90s","video","oldschool","record","tape"]},"magnifying-glass-tilted-left":{"a":"Magnifying Glass Tilted Left","b":"1F50D","j":["find","zoom","detective","search","tool","magnifying","glass"]},"magnifying-glass-tilted-right":{"a":"Magnifying Glass Tilted Right","b":"1F50E","j":["find","zoom","detective","search","tool","magnifying","glass"]},"candle":{"a":"Candle","b":"1F56F","j":["light","fire","wax"]},"light-bulb":{"a":"Light Bulb","b":"1F4A1","j":["electric","electricity","idea","light","comic","bulb"]},"flashlight":{"a":"Flashlight","b":"1F526","j":["electric","torch","sight","night","tool","light","dark","camping"]},"red-paper-lantern":{"a":"Red Paper Lantern","b":"1F3EE","j":["red","paper","halloween","spooky","light","bar","lantern"]},"diya-lamp":{"a":"Diya Lamp","b":"1FA94","j":["oil","lamp","diya","lighting"]},"notebook-with-decorative-cover":{"a":"Notebook with Decorative Cover","b":"1F4D4","j":["decorated","notebook","cover","paper","book","record","study","notes","classroom"]},"closed-book":{"a":"Closed Book","b":"1F4D5","j":["closed","read","learn","textbook","book","knowledge","library"]},"open-book":{"a":"Open Book","b":"1F4D6","j":["open","read","learn","book","literature","knowledge","library","study"]},"green-book":{"a":"Green Book","b":"1F4D7","j":["read","green","book","knowledge","library","study"]},"blue-book":{"a":"Blue Book","b":"1F4D8","j":["read","learn","book","knowledge","library","study","blue"]},"orange-book":{"a":"Orange Book","b":"1F4D9","j":["read","orange","textbook","book","knowledge","library","study"]},"books":{"a":"Books","b":"1F4DA","j":["library","book","study","literature"]},"notebook":{"a":"Notebook","b":"1F4D3","j":["paper","stationery","record","study","notes"]},"ledger":{"a":"Ledger","b":"1F4D2","j":["paper","notes","notebook"]},"page-with-curl":{"a":"Page with Curl","b":"1F4C3","j":["document","paper","documents","page","office","curl"]},"scroll":{"a":"Scroll","b":"1F4DC","j":["paper","documents","ancient","history"]},"page-facing-up":{"a":"Page Facing Up","b":"1F4C4","j":["document","information","paper","documents","page","office"]},"newspaper":{"a":"Newspaper","b":"1F4F0","j":["paper","news","press","headline"]},"rolledup-newspaper":{"a":"Rolled-Up Newspaper","b":"1F5DE","j":["rolled","paper","press","newspaper","news","rolled-up newspaper","rolled_up_newspaper","headline"]},"bookmark-tabs":{"a":"Bookmark Tabs","b":"1F4D1","j":["order","bookmark","tidy","mark","marker","save","favorite","tabs"]},"bookmark":{"a":"Bookmark","b":"1F516","j":["save","label","mark","favorite"]},"label":{"a":"Label","b":"1F3F7","j":["tag","sale"]},"money-bag":{"a":"Money Bag","b":"1F4B0","j":["sale","payment","coins","dollar","moneybag","money","bag"]},"coin":{"a":"Coin","b":"1FA99","j":["gold","currency","money","treasure","silver","metal"]},"yen-banknote":{"a":"Yen Banknote","b":"1F4B4","j":["banknote","currency","bill","note","dollar","money","sales","yen","japanese"]},"dollar-banknote":{"a":"Dollar Banknote","b":"1F4B5","j":["banknote","currency","bill","dollar","note","money","sales"]},"euro-banknote":{"a":"Euro Banknote","b":"1F4B6","j":["banknote","currency","bill","euro","note","dollar","money","sales"]},"pound-banknote":{"a":"Pound Banknote","b":"1F4B7","j":["banknote","uk","currency","bill","british","sterling","note","money","sales","bills","england","pound"]},"money-with-wings":{"a":"Money with Wings","b":"1F4B8","j":["banknote","bill","sale","wings","payment","dollar","money","bills","fly"]},"credit-card":{"a":"Credit Card","b":"1F4B3","j":["credit","card","bill","payment","dollar","money","sales","shopping"]},"receipt":{"a":"Receipt","b":"1F9FE","j":["evidence","proof","accounting","expenses","bookkeeping"]},"chart-increasing-with-yen":{"a":"Chart Increasing with Yen","b":"1F4B9","j":["stats","presentation","growth","green-square","money","chart","yen","graph"]},"envelope":{"a":"Envelope","b":"2709","j":["postal","letter","communication","email","inbox"]},"email":{"a":"E-Mail","b":"1F4E7","j":["letter","communication","e_mail","inbox","e-mail","mail"]},"incoming-envelope":{"a":"Incoming Envelope","b":"1F4E8","j":["letter","envelope","receive","email","incoming","e-mail","inbox"]},"envelope-with-arrow":{"a":"Envelope with Arrow","b":"1F4E9","j":["communication","envelope","email","arrow","outgoing","e-mail"]},"outbox-tray":{"a":"Outbox Tray","b":"1F4E4","j":["letter","sent","email","outbox","tray","box","mail","inbox"]},"inbox-tray":{"a":"Inbox Tray","b":"1F4E5","j":["letter","receive","email","documents","tray","box","mail","inbox"]},"package":{"a":"Package","b":"1F4E6","j":["cardboard","gift","moving","box","parcel","mail"]},"closed-mailbox-with-raised-flag":{"a":"Closed Mailbox with Raised Flag","b":"1F4EB","j":["communication","closed","email","inbox","mailbox","mail","postbox"]},"closed-mailbox-with-lowered-flag":{"a":"Closed Mailbox with Lowered Flag","b":"1F4EA","j":["communication","closed","email","lowered","inbox","mailbox","mail","postbox"]},"open-mailbox-with-raised-flag":{"a":"Open Mailbox with Raised Flag","b":"1F4EC","j":["communication","open","email","inbox","mailbox","mail","postbox"]},"open-mailbox-with-lowered-flag":{"a":"Open Mailbox with Lowered Flag","b":"1F4ED","j":["open","email","lowered","inbox","mailbox","mail","postbox"]},"postbox":{"a":"Postbox","b":"1F4EE","j":["letter","envelope","email","mailbox","mail"]},"ballot-box-with-ballot":{"a":"Ballot Box with Ballot","b":"1F5F3","j":["election","ballot","box","vote"]},"pencil":{"a":"Pencil","b":"270F","j":["paper","school","write","stationery","writing","study"]},"black-nib":{"a":"Black Nib","b":"2712","j":["pen","write","stationery","writing","nib"]},"fountain-pen":{"a":"Fountain Pen","b":"1F58B","j":["pen","write","stationery","fountain","writing"]},"pen":{"a":"Pen","b":"1F58A","j":["stationery","ballpoint","writing","write"]},"paintbrush":{"a":"Paintbrush","b":"1F58C","j":["drawing","art","creativity","painting"]},"crayon":{"a":"Crayon","b":"1F58D","j":["drawing","creativity"]},"memo":{"a":"Memo","b":"1F4DD","j":["test","exam","paper","quiz","write","pencil","stationery","documents","compose","legal","writing","study"]},"briefcase":{"a":"Briefcase","b":"1F4BC","j":["business","documents","work","legal","job","law","career"]},"file-folder":{"a":"File Folder","b":"1F4C1","j":["business","file","folder","documents","office"]},"open-file-folder":{"a":"Open File Folder","b":"1F4C2","j":["open","file","load","folder","documents"]},"card-index-dividers":{"a":"Card Index Dividers","b":"1F5C2","j":["business","card","index","dividers","stationery","organizing"]},"calendar":{"a":"Calendar","b":"1F4C5","j":["date","schedule"]},"tearoff-calendar":{"a":"Tear-off Calendar","b":"1F4C6","j":["tear-off calendar","tear_off_calendar","planning","calendar","date","schedule"]},"spiral-notepad":{"a":"Spiral Notepad","b":"1F5D2","j":["memo","stationery","note","pad","spiral"]},"spiral-calendar":{"a":"Spiral Calendar","b":"1F5D3","j":["pad","planning","calendar","date","spiral","schedule"]},"card-index":{"a":"Card Index","b":"1F4C7","j":["business","card","index","rolodex","stationery"]},"chart-increasing":{"a":"Chart Increasing","b":"1F4C8","j":["business","stats","presentation","growth","success","money","chart","sales","good","recovery","graph","trend","upward","economics"]},"chart-decreasing":{"a":"Chart Decreasing","b":"1F4C9","j":["business","stats","failure","presentation","down","money","chart","recession","sales","bad","graph","trend","economics"]},"bar-chart":{"a":"Bar Chart","b":"1F4CA","j":["stats","presentation","bar","chart","graph"]},"clipboard":{"a":"Clipboard","b":"1F4CB","j":["stationery","documents"]},"pushpin":{"a":"Pushpin","b":"1F4CC","j":["stationery","mark","here","pin"]},"round-pushpin":{"a":"Round Pushpin","b":"1F4CD","j":["pushpin","location","stationery","map","here","pin"]},"paperclip":{"a":"Paperclip","b":"1F4CE","j":["stationery","documents"]},"linked-paperclips":{"a":"Linked Paperclips","b":"1F587","j":["stationery","link","documents","paperclip"]},"straight-ruler":{"a":"Straight Ruler","b":"1F4CF","j":["drawing","architect","sketch","school","math","stationery","calculate","straight edge","ruler","length"]},"triangular-ruler":{"a":"Triangular Ruler","b":"1F4D0","j":["sketch","architect","math","triangle","stationery","ruler","set"]},"scissors":{"a":"Scissors","b":"2702","j":["stationery","tool","cut","cutting"]},"card-file-box":{"a":"Card File Box","b":"1F5C3","j":["business","card","file","stationery","box"]},"file-cabinet":{"a":"File Cabinet","b":"1F5C4","j":["cabinet","file","filing","organizing"]},"wastebasket":{"a":"Wastebasket","b":"1F5D1","j":["rubbish","trash","garbage","bin","toss"]},"locked":{"a":"Locked","b":"1F512","j":["padlock","closed","password","security"]},"unlocked":{"a":"Unlocked","b":"1F513","j":["privacy","unlock","open","lock","security"]},"locked-with-pen":{"a":"Locked with Pen","b":"1F50F","j":["privacy","pen","lock","ink","security","secret","nib"]},"locked-with-key":{"a":"Locked with Key","b":"1F510","j":["privacy","closed","lock","key","security","secure"]},"key":{"a":"Key","b":"1F511","j":["door","password","lock"]},"old-key":{"a":"Old Key","b":"1F5DD","j":["password","lock","old","door","key","clue"]},"hammer":{"a":"Hammer","b":"1F528","j":["create","tools","tool","build"]},"axe":{"a":"Axe","b":"1FA93","j":["chop","hatchet","wood","tool","split","cut"]},"pick":{"a":"Pick","b":"26CF","j":["dig","tools","tool","mining"]},"hammer-and-pick":{"a":"Hammer and Pick","b":"2692","j":["hammer","build","tool","pick","create","tools"]},"hammer-and-wrench":{"a":"Hammer and Wrench","b":"1F6E0","j":["spanner","wrench","hammer","build","tool","create","tools"]},"dagger":{"a":"Dagger","b":"1F5E1","j":["knife","weapon"]},"crossed-swords":{"a":"Crossed Swords","b":"2694","j":["crossed","weapon","swords"]},"water-pistol":{"a":"Water Pistol","b":"1F52B","j":["gun","water","revolver","tool","weapon","handgun","pistol","violence"]},"boomerang":{"a":"Boomerang","b":"1FA83","j":["repercussion","rebound","australia","weapon"]},"bow-and-arrow":{"a":"Bow and Arrow","b":"1F3F9","j":["archer","sports","bow","Sagittarius","arrow","zodiac"]},"shield":{"a":"Shield","b":"1F6E1","j":["protection","security","weapon"]},"carpentry-saw":{"a":"Carpentry Saw","b":"1FA9A","j":["chop","carpenter","tool","cut","lumber","saw"]},"wrench":{"a":"Wrench","b":"1F527","j":["spanner","fix","tool","diy","ikea","maintainer","tools"]},"screwdriver":{"a":"Screwdriver","b":"1FA9B","j":["screw","tools","tool"]},"nut-and-bolt":{"a":"Nut and Bolt","b":"1F529","j":["fix","nut","bolt","tool","handy","tools"]},"gear":{"a":"Gear","b":"2699","j":["cogwheel","tool","cog"]},"clamp":{"a":"Clamp","b":"1F5DC","j":["compress","vice","tool"]},"balance-scale":{"a":"Balance Scale","b":"2696","j":["balance","scale","Libra","weight","law","justice","zodiac","fairness"]},"white-cane":{"a":"White Cane","b":"1F9AF","j":["accessibility","blind","probing_cane"]},"link":{"a":"Link","b":"1F517","j":["rings","url"]},"chains":{"a":"Chains","b":"26D3","j":["arrest","chain","lock"]},"hook":{"a":"Hook","b":"1FA9D","j":["crook","catch","ensnare","curve","selling point","tools"]},"toolbox":{"a":"Toolbox","b":"1F9F0","j":["fix","chest","tool","diy","mechanic","maintainer","tools"]},"magnet":{"a":"Magnet","b":"1F9F2","j":["magnetic","attraction","horseshoe"]},"ladder":{"a":"Ladder","b":"1FA9C","j":["rung","tools","climb","step"]},"alembic":{"a":"Alembic","b":"2697","j":["tool","chemistry","experiment","distilling","science"]},"test-tube":{"a":"Test Tube","b":"1F9EA","j":["chemist","experiment","chemistry","lab","science"]},"petri-dish":{"a":"Petri Dish","b":"1F9EB","j":["bacteria","culture","biology","lab","biologist"]},"dna":{"a":"Dna","b":"1F9EC","j":["genetics","gene","biologist","life","evolution"]},"microscope":{"a":"Microscope","b":"1F52C","j":["tool","experiment","zoomin","laboratory","study","science"]},"telescope":{"a":"Telescope","b":"1F52D","j":["stars","zoom","space","tool","astronomy","science"]},"satellite-antenna":{"a":"Satellite Antenna","b":"1F4E1","j":["communication","space","satellite","future","antenna","radio","dish"]},"syringe":{"a":"Syringe","b":"1F489","j":["sick","needle","drugs","blood","health","nurse","medicine","doctor","shot","hospital"]},"drop-of-blood":{"a":"Drop of Blood","b":"1FA78","j":["period","harm","injury","medicine","menstruation","wound","bleed","blood donation","hurt"]},"pill":{"a":"Pill","b":"1F48A","j":["sick","health","medicine","doctor","drug","pharmacy"]},"adhesive-bandage":{"a":"Adhesive Bandage","b":"1FA79","j":["bandage","heal"]},"stethoscope":{"a":"Stethoscope","b":"1FA7A","j":["medicine","heart","doctor","health"]},"door":{"a":"Door","b":"1F6AA","j":["entry","exit","house"]},"elevator":{"a":"Elevator","b":"1F6D7","j":["accessibility","hoist","lift"]},"mirror":{"a":"Mirror","b":"1FA9E","j":["speculum","reflection","reflector"]},"window":{"a":"Window","b":"1FA9F","j":["frame","transparent","scenery","fresh air","opening","view"]},"bed":{"a":"Bed","b":"1F6CF","j":["rest","hotel","sleep"]},"couch-and-lamp":{"a":"Couch and Lamp","b":"1F6CB","j":["lamp","read","chill","couch","hotel"]},"chair":{"a":"Chair","b":"1FA91","j":["furniture","sit","seat"]},"toilet":{"a":"Toilet","b":"1F6BD","j":["washroom","bathroom","potty","restroom","wc"]},"plunger":{"a":"Plunger","b":"1FAA0","j":["suction","plumber","toilet","force cup"]},"shower":{"a":"Shower","b":"1F6BF","j":["water","bathroom","clean"]},"bathtub":{"a":"Bathtub","b":"1F6C1","j":["shower","bath","bathroom","clean"]},"mouse-trap":{"a":"Mouse Trap","b":"1FAA4","j":["trap","bait","snare","cheese","mousetrap"]},"razor":{"a":"Razor","b":"1FA92","j":["cut","sharp","shave"]},"lotion-bottle":{"a":"Lotion Bottle","b":"1F9F4","j":["moisturizer","lotion","sunscreen","shampoo"]},"safety-pin":{"a":"Safety Pin","b":"1F9F7","j":["diaper","punk rock"]},"broom":{"a":"Broom","b":"1F9F9","j":["cleaning","sweeping","witch"]},"basket":{"a":"Basket","b":"1F9FA","j":["picnic","farming","laundry"]},"roll-of-paper":{"a":"Roll of Paper","b":"1F9FB","j":["roll","paper towels","toilet paper"]},"bucket":{"a":"Bucket","b":"1FAA3","j":["vat","water","container","cask","pail"]},"soap":{"a":"Soap","b":"1F9FC","j":["soapdish","lather","bar","bathing","cleaning"]},"toothbrush":{"a":"Toothbrush","b":"1FAA5","j":["hygiene","teeth","dental","bathroom","clean","brush"]},"sponge":{"a":"Sponge","b":"1F9FD","j":["porous","cleaning","absorbing"]},"fire-extinguisher":{"a":"Fire Extinguisher","b":"1F9EF","j":["fire","extinguish","quench"]},"shopping-cart":{"a":"Shopping Cart","b":"1F6D2","j":["trolley","cart","shopping"]},"cigarette":{"a":"Cigarette","b":"1F6AC","j":["kills","smoking","smoke","joint","tobacco"]},"coffin":{"a":"Coffin","b":"26B0","j":["funeral","cemetery","vampire","rip","die","graveyard","box","death","casket","dead"]},"headstone":{"a":"Headstone","b":"1FAA6","j":["cemetery","rip","graveyard","grave","tombstone","death"]},"funeral-urn":{"a":"Funeral Urn","b":"26B1","j":["funeral","rip","ashes","die","urn","death","dead"]},"moai":{"a":"Moai","b":"1F5FF","j":["moyai","rock","statue","face","easter island"]},"placard":{"a":"Placard","b":"1FAA7","j":["demonstration","picket","sign","announcement","protest"]},"atm-sign":{"a":"Atm Sign","b":"1F3E7","j":["teller","cash","payment","ATM sign","money","sales","automated","blue-square","atm","bank"]},"litter-in-bin-sign":{"a":"Litter in Bin Sign","b":"1F6AE","j":["info","litter bin","sign","blue-square","litter","human"]},"potable-water":{"a":"Potable Water","b":"1F6B0","j":["cleaning","water","restroom","potable","blue-square","drinking","faucet","liquid"]},"wheelchair-symbol":{"a":"Wheelchair Symbol","b":"267F","j":["accessibility","disabled","blue-square","access"]},"mens-room":{"a":"Men’S Room","b":"1F6B9","j":["lavatory","men_s_room","male","man","toilet","gender","restroom","blue-square","men’s room","wc"]},"womens-room":{"a":"Women’S Room","b":"1F6BA","j":["lavatory","purple-square","loo","women_s_room","woman","toilet","gender","female","restroom","women’s room","wc"]},"restroom":{"a":"Restroom","b":"1F6BB","j":["lavatory","toilet","gender","refresh","WC","blue-square","wc"]},"baby-symbol":{"a":"Baby Symbol","b":"1F6BC","j":["orange-square","child","changing","baby"]},"water-closet":{"a":"Water Closet","b":"1F6BE","j":["lavatory","water","toilet","closet","restroom","blue-square","wc"]},"passport-control":{"a":"Passport Control","b":"1F6C2","j":["control","passport","blue-square","custom"]},"customs":{"a":"Customs","b":"1F6C3","j":["border","blue-square","passport"]},"baggage-claim":{"a":"Baggage Claim","b":"1F6C4","j":["baggage","claim","transport","airport","blue-square"]},"left-luggage":{"a":"Left Luggage","b":"1F6C5","j":["baggage","luggage","travel","blue-square","locker"]},"warning":{"a":"Warning","b":"26A0","j":["exclamation","wip","issue","error","problem","alert"]},"children-crossing":{"a":"Children Crossing","b":"1F6B8","j":["traffic","driving","school","sign","child","pedestrian","crossing","warning","danger","yellow-diamond"]},"no-entry":{"a":"No Entry","b":"26D4","j":["privacy","traffic","limit","stop","no","not","prohibited","denied","entry","circle","bad","security","forbidden"]},"prohibited":{"a":"Prohibited","b":"1F6AB","j":["limit","stop","disallow","no","not","denied","forbid","entry","circle","forbidden"]},"no-bicycles":{"a":"No Bicycles","b":"1F6B3","j":["cyclist","no","bike","prohibited","bicycle","circle","forbidden"]},"no-smoking":{"a":"No Smoking","b":"1F6AD","j":["cigarette","no","prohibited","not","smoking","smell","smoke","blue-square","forbidden"]},"no-littering":{"a":"No Littering","b":"1F6AF","j":["trash","not","no","prohibited","garbage","bin","circle","litter","forbidden"]},"nonpotable-water":{"a":"Non-Potable Water","b":"1F6B1","j":["non_potable_water","faucet","water","drink","circle","tap","non-drinking","non-potable"]},"no-pedestrians":{"a":"No Pedestrians","b":"1F6B7","j":["no","not","prohibited","rules","circle","walking","pedestrian","crossing","forbidden"]},"no-mobile-phones":{"a":"No Mobile Phones","b":"1F4F5","j":["mobile","no","mute","cell","circle","iphone","forbidden","phone"]},"no-one-under-eighteen":{"a":"No One Under Eighteen","b":"1F51E","j":["night","prohibited","underage","eighteen","drink","circle","18","minor","pub","age restriction"]},"radioactive":{"a":"Radioactive","b":"2622","j":["nuclear","danger","sign"]},"biohazard":{"a":"Biohazard","b":"2623","j":["danger","sign"]},"up-arrow":{"a":"Up Arrow","b":"2B06","j":["north","arrow","direction","cardinal","continue","top","blue-square"]},"upright-arrow":{"a":"Up-Right Arrow","b":"2197","j":["point","up_right_arrow","arrow","direction","northeast","diagonal","blue-square","intercardinal","up-right arrow"]},"right-arrow":{"a":"Right Arrow","b":"27A1","j":["arrow","direction","cardinal","east","next","blue-square"]},"downright-arrow":{"a":"Down-Right Arrow","b":"2198","j":["down_right_arrow","arrow","direction","southeast","diagonal","blue-square","down-right arrow","intercardinal"]},"down-arrow":{"a":"Down Arrow","b":"2B07","j":["south","arrow","direction","cardinal","down","blue-square","bottom"]},"downleft-arrow":{"a":"Down-Left Arrow","b":"2199","j":["arrow","direction","down_left_arrow","down-left arrow","southwest","diagonal","blue-square","intercardinal"]},"left-arrow":{"a":"Left Arrow","b":"2B05","j":["previous","arrow","cardinal","direction","west","blue-square","back"]},"upleft-arrow":{"a":"Up-Left Arrow","b":"2196","j":["point","arrow","direction","northwest","diagonal","blue-square","intercardinal","up_left_arrow","up-left arrow"]},"updown-arrow":{"a":"Up-Down Arrow","b":"2195","j":["arrow","direction","way","up_down_arrow","blue-square","vertical","up-down arrow"]},"leftright-arrow":{"a":"Left-Right Arrow","b":"2194","j":["sideways","horizontal","left-right arrow","arrow","direction","shape","left_right_arrow"]},"right-arrow-curving-left":{"a":"Right Arrow Curving Left","b":"21A9","j":["enter","arrow","return","blue-square","undo","back"]},"left-arrow-curving-right":{"a":"Left Arrow Curving Right","b":"21AA","j":["arrow","direction","rotate","return","blue-square"]},"right-arrow-curving-up":{"a":"Right Arrow Curving Up","b":"2934","j":["top","arrow","direction","blue-square"]},"right-arrow-curving-down":{"a":"Right Arrow Curving Down","b":"2935","j":["arrow","direction","down","blue-square","bottom"]},"clockwise-vertical-arrows":{"a":"Clockwise Vertical Arrows","b":"1F503","j":["reload","clockwise","arrow","cycle","repeat","sync","round"]},"counterclockwise-arrows-button":{"a":"Counterclockwise Arrows Button","b":"1F504","j":["withershins","arrow","cycle","sync","blue-square","anticlockwise","counterclockwise"]},"back-arrow":{"a":"Back Arrow","b":"1F519","j":["arrow","return","words","BACK arrow","back"]},"end-arrow":{"a":"End Arrow","b":"1F51A","j":["words","arrow","END arrow","end"]},"on-arrow":{"a":"On! Arrow","b":"1F51B","j":["on","arrow","words","ON! arrow","mark"]},"soon-arrow":{"a":"Soon Arrow","b":"1F51C","j":["words","arrow","SOON arrow","soon"]},"top-arrow":{"a":"Top Arrow","b":"1F51D","j":["up","TOP arrow","top","arrow","words","blue-square"]},"place-of-worship":{"a":"Place of Worship","b":"1F6D0","j":["church","worship","religion","temple","prayer"]},"atom-symbol":{"a":"Atom Symbol","b":"269B","j":["atheist","atom","chemistry","physics","science"]},"om":{"a":"Om","b":"1F549","j":["hinduism","religion","sikhism","jainism","Hindu","buddhism"]},"star-of-david":{"a":"Star of David","b":"2721","j":["Jew","star of David","religion","Jewish","David","judaism","star"]},"wheel-of-dharma":{"a":"Wheel of Dharma","b":"2638","j":["dharma","hinduism","Buddhist","wheel","religion","sikhism","jainism","buddhism"]},"yin-yang":{"a":"Yin Yang","b":"262F","j":["taoist","balance","religion","yang","yin","tao"]},"latin-cross":{"a":"Latin Cross","b":"271D","j":["religion","Christian","christianity","cross"]},"orthodox-cross":{"a":"Orthodox Cross","b":"2626","j":["religion","suppedaneum","Christian","cross"]},"star-and-crescent":{"a":"Star and Crescent","b":"262A","j":["religion","islam","Muslim"]},"peace-symbol":{"a":"Peace Symbol","b":"262E","j":["peace","hippie"]},"menorah":{"a":"Menorah","b":"1F54E","j":["religion","candelabrum","jewish","candlestick","hanukkah","candles"]},"dotted-sixpointed-star":{"a":"Dotted Six-Pointed Star","b":"1F52F","j":["purple-square","dotted six-pointed star","dotted_six_pointed_star","religion","jewish","hexagram","fortune","star"]},"aries":{"a":"Aries","b":"2648","j":["purple-square","sign","ram","astrology","zodiac"]},"taurus":{"a":"Taurus","b":"2649","j":["purple-square","ox","sign","bull","astrology","zodiac"]},"gemini":{"a":"Gemini","b":"264A","j":["purple-square","twins","sign","astrology","zodiac"]},"cancer":{"a":"Cancer","b":"264B","j":["purple-square","sign","astrology","crab","zodiac"]},"leo":{"a":"Leo","b":"264C","j":["purple-square","lion","sign","astrology","zodiac"]},"virgo":{"a":"Virgo","b":"264D","j":["zodiac","purple-square","sign","astrology"]},"libra":{"a":"Libra","b":"264E","j":["balance","purple-square","scales","sign","astrology","justice","zodiac"]},"scorpio":{"a":"Scorpio","b":"264F","j":["purple-square","scorpius","sign","astrology","zodiac","scorpion"]},"sagittarius":{"a":"Sagittarius","b":"2650","j":["purple-square","archer","sign","astrology","zodiac"]},"capricorn":{"a":"Capricorn","b":"2651","j":["purple-square","sign","goat","astrology","zodiac"]},"aquarius":{"a":"Aquarius","b":"2652","j":["purple-square","water","sign","astrology","bearer","zodiac"]},"pisces":{"a":"Pisces","b":"2653","j":["fish","purple-square","sign","astrology","zodiac"]},"ophiuchus":{"a":"Ophiuchus","b":"26CE","j":["purple-square","constellation","sign","astrology","snake","bearer","serpent","zodiac"]},"shuffle-tracks-button":{"a":"Shuffle Tracks Button","b":"1F500","j":["shuffle","random","arrow","music","blue-square","crossed"]},"repeat-button":{"a":"Repeat Button","b":"1F501","j":["loop","clockwise","arrow","repeat","record"]},"repeat-single-button":{"a":"Repeat Single Button","b":"1F502","j":["loop","clockwise","arrow","once","blue-square"]},"play-button":{"a":"Play Button","b":"25B6","j":["right","arrow","direction","triangle","blue-square","play"]},"fastforward-button":{"a":"Fast-Forward Button","b":"23E9","j":["speed","forward","arrow","fast_forward_button","continue","double","fast","blue-square","fast-forward button","play"]},"next-track-button":{"a":"Next Track Button","b":"23ED","j":["next scene","forward","arrow","triangle","next","blue-square","next track"]},"play-or-pause-button":{"a":"Play or Pause Button","b":"23EF","j":["right","arrow","triangle","blue-square","pause","play"]},"reverse-button":{"a":"Reverse Button","b":"25C0","j":["left","arrow","direction","triangle","reverse","blue-square"]},"fast-reverse-button":{"a":"Fast Reverse Button","b":"23EA","j":["arrow","double","blue-square","rewind","play"]},"last-track-button":{"a":"Last Track Button","b":"23EE","j":["previous track","arrow","triangle","backward","previous scene"]},"upwards-button":{"a":"Upwards Button","b":"1F53C","j":["point","red","forward","arrow","direction","top","triangle","button","blue-square"]},"fast-up-button":{"a":"Fast Up Button","b":"23EB","j":["top","arrow","direction","double","blue-square"]},"downwards-button":{"a":"Downwards Button","b":"1F53D","j":["red","arrow","direction","down","button","blue-square","bottom"]},"fast-down-button":{"a":"Fast Down Button","b":"23EC","j":["arrow","direction","down","double","blue-square","bottom"]},"pause-button":{"a":"Pause Button","b":"23F8","j":["double","bar","blue-square","pause","vertical"]},"stop-button":{"a":"Stop Button","b":"23F9","j":["square","stop","blue-square"]},"record-button":{"a":"Record Button","b":"23FA","j":["record","circle","blue-square"]},"eject-button":{"a":"Eject Button","b":"23CF","j":["eject","blue-square"]},"cinema":{"a":"Cinema","b":"1F3A6","j":["movie","camera","film","stage","curtain","blue-square","record","theater"]},"dim-button":{"a":"Dim Button","b":"1F505","j":["dim","low","warm","sun","summer","afternoon","brightness"]},"bright-button":{"a":"Bright Button","b":"1F506","j":["light","sun","brightness","bright"]},"antenna-bars":{"a":"Antenna Bars","b":"1F4F6","j":["mobile","internet","connection","wifi","cell","bar","bars","reception","blue-square","antenna","bluetooth","phone"]},"vibration-mode":{"a":"Vibration Mode","b":"1F4F3","j":["orange-square","mobile","mode","vibration","cell","telephone","phone"]},"mobile-phone-off":{"a":"Mobile Phone off","b":"1F4F4","j":["orange-square","mobile","mute","cell","off","telephone","silence","quiet","phone"]},"female-sign":{"a":"Female Sign","b":"2640","j":["woman","women","girl","lady"]},"male-sign":{"a":"Male Sign","b":"2642","j":["man","boy","men"]},"transgender-symbol":{"a":"Transgender Symbol","b":"26A7","j":["lgbtq","transgender"]},"multiply":{"a":"Multiply","b":"2716","j":["cancel","multiplication_sign","x","math","sign","multiplication","calculation","×"]},"plus":{"a":"Plus","b":"2795","j":["+","addition","math","more","sign","increase","calculation","plus_sign"]},"minus":{"a":"Minus","b":"2796","j":["-","math","less","sign","−","minus_sign","subtract","calculation"]},"divide":{"a":"Divide","b":"2797","j":["÷","math","division","sign","calculation","division_sign"]},"infinity":{"a":"Infinity","b":"267E","j":["universal","forever","unbounded"]},"double-exclamation-mark":{"a":"Double Exclamation Mark","b":"203C","j":["exclamation","surprise","!","mark","!!","bangbang"]},"exclamation-question-mark":{"a":"Exclamation Question Mark","b":"2049","j":["exclamation","surprise","wat","punctuation","!","?","mark","question","interrobang","!?"]},"red-question-mark":{"a":"Red Question Mark","b":"2753","j":["punctuation","question_mark","confused","question","?","mark","doubt"]},"white-question-mark":{"a":"White Question Mark","b":"2754","j":["outlined","gray","punctuation","doubts","confused","question","?","huh","mark"]},"white-exclamation-mark":{"a":"White Exclamation Mark","b":"2755","j":["exclamation","surprise","wow","outlined","gray","punctuation","!","mark","warning"]},"red-exclamation-mark":{"a":"Red Exclamation Mark","b":"2757","j":["exclamation","surprise","wow","punctuation","exclamation_mark","!","mark","warning","danger","heavy_exclamation_mark"]},"wavy-dash":{"a":"Wavy Dash","b":"3030","j":["draw","punctuation","mustache","line","moustache","squiggle","wavy","scribble","dash"]},"currency-exchange":{"a":"Currency Exchange","b":"1F4B1","j":["exchange","currency","dollar","money","sales","travel","bank"]},"heavy-dollar-sign":{"a":"Heavy Dollar Sign","b":"1F4B2","j":["currency","buck","payment","dollar","money","sales"]},"medical-symbol":{"a":"Medical Symbol","b":"2695","j":["health","medicine","staff","aesculapius","hospital"]},"recycling-symbol":{"a":"Recycling Symbol","b":"267B","j":["recycle","trash","garbage","arrow","environment"]},"fleurdelis":{"a":"Fleur-De-Lis","b":"269C","j":["scout","fleur-de-lis","decorative","fleur_de_lis"]},"trident-emblem":{"a":"Trident Emblem","b":"1F531","j":["trident","ship","spear","tool","emblem","weapon","anchor"]},"name-badge":{"a":"Name Badge","b":"1F4DB","j":["name","fire","badge","forbid"]},"japanese-symbol-for-beginner":{"a":"Japanese Symbol for Beginner","b":"1F530","j":["shield","badge","leaf","Japanese","chevron","Japanese symbol for beginner","beginner"]},"hollow-red-circle":{"a":"Hollow Red Circle","b":"2B55","j":["red","o","large","circle","round"]},"check-mark-button":{"a":"Check Mark Button","b":"2705","j":["ok","answer","check","green-square","tick","election","✓","button","mark","vote","agree"]},"check-box-with-check":{"a":"Check Box with Check","b":"2611","j":["confirm","ok","black-square","check","tick","election","✓","box","vote","yes","agree"]},"check-mark":{"a":"Check Mark","b":"2714","j":["ok","answer","check","nike","tick","✓","mark","yes"]},"cross-mark":{"a":"Cross Mark","b":"274C","j":["cancel","multiply","red","no","x","cross","delete","remove","multiplication","mark","×"]},"cross-mark-button":{"a":"Cross Mark Button","b":"274E","j":["no","x","green-square","mark","×","square","deny"]},"curly-loop":{"a":"Curly Loop","b":"27B0","j":["loop","draw","shape","squiggle","scribble","curl"]},"double-curly-loop":{"a":"Double Curly Loop","b":"27BF","j":["loop","cassette","double","tape","curl"]},"part-alternation-mark":{"a":"Part Alternation Mark","b":"303D","j":["business","stats","presentation","bad","mark","graph","part","economics"]},"eightspoked-asterisk":{"a":"Eight-Spoked Asterisk","b":"2733","j":["*","sparkle","green-square","eight_spoked_asterisk","asterisk","eight-spoked asterisk","star"]},"eightpointed-star":{"a":"Eight-Pointed Star","b":"2734","j":["*","orange-square","polygon","eight_pointed_star","eight-pointed star","shape","star"]},"sparkle":{"a":"Sparkle","b":"2747","j":["*","stars","fireworks","awesome","green-square","good"]},"copyright":{"a":"Copyright","b":"00A9","j":["license","ip","circle","c","legal","law"]},"registered":{"a":"Registered","b":"00AE","j":["alphabet","circle","r"]},"trade-mark":{"a":"Trade Mark","b":"2122","j":["trademark","brand","tm","mark","legal","law"]},"keycap":{"a":"Keycap: *","b":"002A-FE0F-20E3","j":["star","keycap_"]},"keycap-0":{"a":"Keycap: 0","b":"0030-FE0F-20E3","j":["null","numbers","0","blue-square","keycap"]},"keycap-1":{"a":"Keycap: 1","b":"0031-FE0F-20E3","j":["1","numbers","keycap","blue-square"]},"keycap-2":{"a":"Keycap: 2","b":"0032-FE0F-20E3","j":["numbers","prime","blue-square","keycap","2"]},"keycap-3":{"a":"Keycap: 3","b":"0033-FE0F-20E3","j":["numbers","3","prime","blue-square","keycap"]},"keycap-4":{"a":"Keycap: 4","b":"0034-FE0F-20E3","j":["numbers","keycap","4","blue-square"]},"keycap-5":{"a":"Keycap: 5","b":"0035-FE0F-20E3","j":["numbers","5","prime","blue-square","keycap"]},"keycap-6":{"a":"Keycap: 6","b":"0036-FE0F-20E3","j":["6","numbers","keycap","blue-square"]},"keycap-7":{"a":"Keycap: 7","b":"0037-FE0F-20E3","j":["numbers","7","prime","blue-square","keycap"]},"keycap-8":{"a":"Keycap: 8","b":"0038-FE0F-20E3","j":["numbers","keycap","8","blue-square"]},"keycap-9":{"a":"Keycap: 9","b":"0039-FE0F-20E3","j":["numbers","keycap","9","blue-square"]},"keycap-10":{"a":"Keycap: 10","b":"1F51F","j":["10","numbers","keycap","blue-square"]},"input-latin-uppercase":{"a":"Input Latin Uppercase","b":"1F520","j":["alphabet","ABCD","uppercase","words","blue-square","letters","latin","input"]},"input-latin-lowercase":{"a":"Input Latin Lowercase","b":"1F521","j":["alphabet","abcd","blue-square","letters","latin","input","lowercase"]},"input-numbers":{"a":"Input Numbers","b":"1F522","j":["numbers","1234","input","blue-square"]},"input-symbols":{"a":"Input Symbols","b":"1F523","j":["〒♪&%","characters","glyphs","note","music","blue-square","percent","ampersand","input"]},"input-latin-letters":{"a":"Input Latin Letters","b":"1F524","j":["alphabet","abc","blue-square","letters","latin","input"]},"a-button-blood-type":{"a":"A Button (Blood Type)","b":"1F170","j":["a","letter","alphabet","a_button","red-square","A button (blood type)","blood type"]},"ab-button-blood-type":{"a":"Ab Button (Blood Type)","b":"1F18E","j":["ab","alphabet","ab_button","red-square","AB button (blood type)","blood type"]},"b-button-blood-type":{"a":"B Button (Blood Type)","b":"1F171","j":["letter","alphabet","b","B button (blood type)","red-square","b_button","blood type"]},"cl-button":{"a":"Cl Button","b":"1F191","j":["cl","alphabet","CL button","words","red-square"]},"cool-button":{"a":"Cool Button","b":"1F192","j":["words","cool","COOL button","blue-square"]},"free-button":{"a":"Free Button","b":"1F193","j":["words","FREE button","free","blue-square"]},"information":{"a":"Information","b":"2139","j":["alphabet","letter","i","blue-square"]},"id-button":{"a":"Id Button","b":"1F194","j":["purple-square","id","ID button","words","identity"]},"circled-m":{"a":"Circled M","b":"24C2","j":["blue-circle","letter","alphabet","circle","circled M","m"]},"new-button":{"a":"New Button","b":"1F195","j":["start","words","NEW button","blue-square","new"]},"ng-button":{"a":"Ng Button","b":"1F196","j":["NG button","ng","icon","words","shape","blue-square"]},"o-button-blood-type":{"a":"O Button (Blood Type)","b":"1F17E","j":["letter","O button (blood type)","o","alphabet","o_button","red-square","blood type"]},"ok-button":{"a":"Ok Button","b":"1F197","j":["OK button","good","blue-square","OK","yes","agree"]},"p-button":{"a":"P Button","b":"1F17F","j":["cars","letter","alphabet","blue-square","parking","P button"]},"sos-button":{"a":"Sos Button","b":"1F198","j":["SOS button","911","help","words","emergency","red-square","sos"]},"up-button":{"a":"Up! Button","b":"1F199","j":["up","high","above","mark","blue-square","UP! button"]},"vs-button":{"a":"Vs Button","b":"1F19A","j":["orange-square","vs","words","VS button","versus"]},"japanese-here-button":{"a":"Japanese “Here” Button","b":"1F201","j":["Japanese “here” button","Japanese","destination","katakana","“here”","blue-square","japanese","ココ","here"]},"japanese-service-charge-button":{"a":"Japanese “Service Charge” Button","b":"1F202","j":["“service charge”","Japanese","katakana","Japanese “service charge” button","サ","blue-square","japanese"]},"japanese-monthly-amount-button":{"a":"Japanese “Monthly Amount” Button","b":"1F237","j":["orange-square","month","Japanese","Japanese “monthly amount” button","“monthly amount”","chinese","月","kanji","japanese","ideograph","moon"]},"japanese-not-free-of-charge-button":{"a":"Japanese “Not Free of Charge” Button","b":"1F236","j":["orange-square","有","“not free of charge”","Japanese","Japanese “not free of charge” button","chinese","kanji","ideograph","have"]},"japanese-reserved-button":{"a":"Japanese “Reserved” Button","b":"1F22F","j":["point","Japanese “reserved” button","green-square","Japanese","chinese","指","kanji","ideograph","“reserved”"]},"japanese-bargain-button":{"a":"Japanese “Bargain” Button","b":"1F250","j":["“bargain”","得","Japanese “bargain” button","Japanese","get","circle","chinese","kanji","ideograph","obtain"]},"japanese-discount-button":{"a":"Japanese “Discount” Button","b":"1F239","j":["“discount”","Japanese","Japanese “discount” button","divide","chinese","kanji","ideograph","pink-square","cut","割"]},"japanese-free-of-charge-button":{"a":"Japanese “Free of Charge” Button","b":"1F21A","j":["“free of charge”","orange-square","無","Japanese","Japanese “free of charge” button","chinese","kanji","japanese","ideograph","nothing"]},"japanese-prohibited-button":{"a":"Japanese “Prohibited” Button","b":"1F232","j":["禁","limit","forbidden","“prohibited”","Japanese “prohibited” button","Japanese","restricted","kanji","chinese","japanese","red-square","ideograph"]},"japanese-acceptable-button":{"a":"Japanese “Acceptable” Button","b":"1F251","j":["ok","Japanese “acceptable” button","Japanese","可","chinese","good","kanji","ideograph","orange-circle","yes","“acceptable”","agree"]},"japanese-application-button":{"a":"Japanese “Application” Button","b":"1F238","j":["orange-square","申","“application”","Japanese","chinese","kanji","japanese","Japanese “application” button","ideograph"]},"japanese-passing-grade-button":{"a":"Japanese “Passing Grade” Button","b":"1F234","j":["“passing grade”","Japanese “passing grade” button","Japanese","join","chinese","kanji","合","japanese","red-square","ideograph"]},"japanese-vacancy-button":{"a":"Japanese “Vacancy” Button","b":"1F233","j":["Japanese “vacancy” button","Japanese","sky","japanese","kanji","chinese","empty","blue-square","“vacancy”","空","ideograph"]},"japanese-congratulations-button":{"a":"Japanese “Congratulations” Button","b":"3297","j":["Japanese","祝","Japanese “congratulations” button","chinese","kanji","“congratulations”","japanese","ideograph","red-circle"]},"japanese-secret-button":{"a":"Japanese “Secret” Button","b":"3299","j":["Japanese “secret” button","privacy","sshh","秘","Japanese","chinese","kanji","ideograph","“secret”","red-circle"]},"japanese-open-for-business-button":{"a":"Japanese “Open for Business” Button","b":"1F23A","j":["“open for business”","orange-square","opening hours","Japanese “open for business” button","Japanese","営","japanese","ideograph"]},"japanese-no-vacancy-button":{"a":"Japanese “No Vacancy” Button","b":"1F235","j":["full","満","Japanese","“no vacancy”","chinese","kanji","japanese","red-square","ideograph","Japanese “no vacancy” button"]},"red-circle":{"a":"Red Circle","b":"1F534","j":["geometric","red","shape","error","circle","danger"]},"orange-circle":{"a":"Orange Circle","b":"1F7E0","j":["circle","round","orange"]},"yellow-circle":{"a":"Yellow Circle","b":"1F7E1","j":["yellow","circle","round"]},"green-circle":{"a":"Green Circle","b":"1F7E2","j":["green","circle","round"]},"blue-circle":{"a":"Blue Circle","b":"1F535","j":["geometric","icon","shape","button","circle","blue"]},"purple-circle":{"a":"Purple Circle","b":"1F7E3","j":["circle","round","purple"]},"brown-circle":{"a":"Brown Circle","b":"1F7E4","j":["brown","circle","round"]},"black-circle":{"a":"Black Circle","b":"26AB","j":["geometric","shape","button","circle","round"]},"white-circle":{"a":"White Circle","b":"26AA","j":["shape","geometric","round","circle"]},"red-square":{"a":"Red Square","b":"1F7E5","j":["square","red"]},"orange-square":{"a":"Orange Square","b":"1F7E7","j":["square","orange"]},"yellow-square":{"a":"Yellow Square","b":"1F7E8","j":["yellow","square"]},"green-square":{"a":"Green Square","b":"1F7E9","j":["green","square"]},"blue-square":{"a":"Blue Square","b":"1F7E6","j":["square","blue"]},"purple-square":{"a":"Purple Square","b":"1F7EA","j":["square","purple"]},"brown-square":{"a":"Brown Square","b":"1F7EB","j":["brown","square"]},"black-large-square":{"a":"Black Large Square","b":"2B1B","j":["geometric","icon","shape","button","square"]},"white-large-square":{"a":"White Large Square","b":"2B1C","j":["geometric","icon","stone","shape","button","square"]},"black-medium-square":{"a":"Black Medium Square","b":"25FC","j":["geometric","icon","shape","button","square"]},"white-medium-square":{"a":"White Medium Square","b":"25FB","j":["geometric","icon","stone","shape","square"]},"black-mediumsmall-square":{"a":"Black Medium-Small Square","b":"25FE","j":["black medium-small square","geometric","icon","black_medium_small_square","shape","button","square"]},"white-mediumsmall-square":{"a":"White Medium-Small Square","b":"25FD","j":["geometric","white medium-small square","white_medium_small_square","icon","stone","shape","button","square"]},"black-small-square":{"a":"Black Small Square","b":"25AA","j":["icon","shape","square","geometric"]},"white-small-square":{"a":"White Small Square","b":"25AB","j":["icon","shape","square","geometric"]},"large-orange-diamond":{"a":"Large Orange Diamond","b":"1F536","j":["geometric","gem","jewel","orange","shape","diamond"]},"large-blue-diamond":{"a":"Large Blue Diamond","b":"1F537","j":["geometric","gem","jewel","shape","diamond","blue"]},"small-orange-diamond":{"a":"Small Orange Diamond","b":"1F538","j":["geometric","gem","jewel","orange","shape","diamond"]},"small-blue-diamond":{"a":"Small Blue Diamond","b":"1F539","j":["geometric","gem","jewel","shape","diamond","blue"]},"red-triangle-pointed-up":{"a":"Red Triangle Pointed Up","b":"1F53A","j":["up","geometric","red","top","direction","shape"]},"red-triangle-pointed-down":{"a":"Red Triangle Pointed Down","b":"1F53B","j":["geometric","red","direction","down","shape","bottom"]},"diamond-with-a-dot":{"a":"Diamond with a Dot","b":"1F4A0","j":["geometric","gem","fancy","jewel","inside","comic","diamond","crystal","blue"]},"radio-button":{"a":"Radio Button","b":"1F518","j":["geometric","old","button","circle","music","radio","input"]},"white-square-button":{"a":"White Square Button","b":"1F533","j":["geometric","outlined","shape","button","square","input"]},"black-square-button":{"a":"Black Square Button","b":"1F532","j":["frame","geometric","shape","button","square","input"]},"chequered-flag":{"a":"Chequered Flag","b":"1F3C1","j":["chequered","checkered","race","racing","gokart","finishline","contest"]},"triangular-flag":{"a":"Triangular Flag","b":"1F6A9","j":["milestone","place","mark","post"]},"crossed-flags":{"a":"Crossed Flags","b":"1F38C","j":["nation","celebration","cross","Japanese","border","japanese","country","crossed"]},"black-flag":{"a":"Black Flag","b":"1F3F4","j":["waving","pirate"]},"white-flag":{"a":"White Flag","b":"1F3F3","j":["loser","give up","surrender","lost","waving","losing","fail"]},"rainbow-flag":{"a":"Rainbow Flag","b":"1F3F3-FE0F-200D-1F308","j":["gay","glbt","pride","lesbian","homosexual","flag","lgbt","bisexual","rainbow","queer","transgender"]},"transgender-flag":{"a":"Transgender Flag","b":"1F3F3-FE0F-200D-26A7-FE0F","j":["pink","lgbtq","light blue","flag","white","transgender"]},"pirate-flag":{"a":"Pirate Flag","b":"1F3F4-200D-2620-FE0F","j":["skull","crossbones","plunder","flag","Jolly Roger","treasure","banner","pirate"]},"flag-ascension-island":{"a":"Flag: Ascension Island","b":"1F1E6-1F1E8","j":["flag"]},"flag-andorra":{"a":"Flag: Andorra","b":"1F1E6-1F1E9","j":["flag","ad","banner","country","nation"]},"flag-united-arab-emirates":{"a":"Flag: United Arab Emirates","b":"1F1E6-1F1EA","j":["united","flag","emirates","banner","arab","country","nation"]},"flag-afghanistan":{"a":"Flag: Afghanistan","b":"1F1E6-1F1EB","j":["af","flag","banner","country","nation"]},"flag-antigua--barbuda":{"a":"Flag: Antigua & Barbuda","b":"1F1E6-1F1EC","j":["barbuda","antigua","flag","banner","country","nation","flag_antigua_barbuda"]},"flag-anguilla":{"a":"Flag: Anguilla","b":"1F1E6-1F1EE","j":["ai","flag","banner","country","nation"]},"flag-albania":{"a":"Flag: Albania","b":"1F1E6-1F1F1","j":["al","flag","banner","country","nation"]},"flag-armenia":{"a":"Flag: Armenia","b":"1F1E6-1F1F2","j":["country","flag","banner","am","nation"]},"flag-angola":{"a":"Flag: Angola","b":"1F1E6-1F1F4","j":["ao","flag","banner","country","nation"]},"flag-antarctica":{"a":"Flag: Antarctica","b":"1F1E6-1F1F6","j":["aq","flag","banner","country","nation"]},"flag-argentina":{"a":"Flag: Argentina","b":"1F1E6-1F1F7","j":["ar","flag","banner","country","nation"]},"flag-american-samoa":{"a":"Flag: American Samoa","b":"1F1E6-1F1F8","j":["ws","flag","banner","country","nation","american"]},"flag-austria":{"a":"Flag: Austria","b":"1F1E6-1F1F9","j":["at","flag","banner","country","nation"]},"flag-australia":{"a":"Flag: Australia","b":"1F1E6-1F1FA","j":["flag","banner","au","country","nation"]},"flag-aruba":{"a":"Flag: Aruba","b":"1F1E6-1F1FC","j":["aw","flag","banner","country","nation"]},"flag-land-islands":{"a":"Flag: Åland Islands","b":"1F1E6-1F1FD","j":["islands","flag","flag_aland_islands","Åland","banner","country","nation"]},"flag-azerbaijan":{"a":"Flag: Azerbaijan","b":"1F1E6-1F1FF","j":["az","flag","banner","country","nation"]},"flag-bosnia--herzegovina":{"a":"Flag: Bosnia & Herzegovina","b":"1F1E7-1F1E6","j":["bosnia","flag_bosnia_herzegovina","flag","herzegovina","banner","country","nation"]},"flag-barbados":{"a":"Flag: Barbados","b":"1F1E7-1F1E7","j":["bb","flag","banner","country","nation"]},"flag-bangladesh":{"a":"Flag: Bangladesh","b":"1F1E7-1F1E9","j":["bd","flag","banner","country","nation"]},"flag-belgium":{"a":"Flag: Belgium","b":"1F1E7-1F1EA","j":["country","flag","banner","be","nation"]},"flag-burkina-faso":{"a":"Flag: Burkina Faso","b":"1F1E7-1F1EB","j":["flag","burkina","banner","faso","country","nation"]},"flag-bulgaria":{"a":"Flag: Bulgaria","b":"1F1E7-1F1EC","j":["flag","banner","bg","country","nation"]},"flag-bahrain":{"a":"Flag: Bahrain","b":"1F1E7-1F1ED","j":["bh","flag","banner","country","nation"]},"flag-burundi":{"a":"Flag: Burundi","b":"1F1E7-1F1EE","j":["flag","banner","bi","country","nation"]},"flag-benin":{"a":"Flag: Benin","b":"1F1E7-1F1EF","j":["country","flag","banner","bj","nation"]},"flag-st-barthlemy":{"a":"Flag: St. Barthélemy","b":"1F1E7-1F1F1","j":["flag_st_barthelemy","saint","flag","banner","country","barthélemy","nation"]},"flag-bermuda":{"a":"Flag: Bermuda","b":"1F1E7-1F1F2","j":["bm","flag","banner","country","nation"]},"flag-brunei":{"a":"Flag: Brunei","b":"1F1E7-1F1F3","j":["darussalam","bn","flag","banner","country","nation"]},"flag-bolivia":{"a":"Flag: Bolivia","b":"1F1E7-1F1F4","j":["country","flag","banner","bo","nation"]},"flag-caribbean-netherlands":{"a":"Flag: Caribbean Netherlands","b":"1F1E7-1F1F6","j":["flag","banner","country","bonaire","nation"]},"flag-brazil":{"a":"Flag: Brazil","b":"1F1E7-1F1F7","j":["flag","banner","br","country","nation"]},"flag-bahamas":{"a":"Flag: Bahamas","b":"1F1E7-1F1F8","j":["bs","flag","banner","country","nation"]},"flag-bhutan":{"a":"Flag: Bhutan","b":"1F1E7-1F1F9","j":["bt","flag","banner","country","nation"]},"flag-bouvet-island":{"a":"Flag: Bouvet Island","b":"1F1E7-1F1FB","j":["flag","norway"]},"flag-botswana":{"a":"Flag: Botswana","b":"1F1E7-1F1FC","j":["flag","bw","banner","country","nation"]},"flag-belarus":{"a":"Flag: Belarus","b":"1F1E7-1F1FE","j":["country","flag","banner","by","nation"]},"flag-belize":{"a":"Flag: Belize","b":"1F1E7-1F1FF","j":["flag","banner","bz","country","nation"]},"flag-canada":{"a":"Flag: Canada","b":"1F1E8-1F1E6","j":["ca","flag","banner","country","nation"]},"flag-cocos-keeling-islands":{"a":"Flag: Cocos (Keeling) Islands","b":"1F1E8-1F1E8","j":["islands","cocos","flag","keeling","banner","flag_cocos_islands","country","nation"]},"flag-congo--kinshasa":{"a":"Flag: Congo - Kinshasa","b":"1F1E8-1F1E9","j":["democratic","congo","flag","banner","flag_congo_kinshasa","republic","country","nation"]},"flag-central-african-republic":{"a":"Flag: Central African Republic","b":"1F1E8-1F1EB","j":["african","central","flag","banner","republic","country","nation"]},"flag-congo--brazzaville":{"a":"Flag: Congo - Brazzaville","b":"1F1E8-1F1EC","j":["flag_congo_brazzaville","congo","flag","banner","country","nation"]},"flag-switzerland":{"a":"Flag: Switzerland","b":"1F1E8-1F1ED","j":["ch","flag","banner","country","nation"]},"flag-cte-divoire":{"a":"Flag: Côte D’Ivoire","b":"1F1E8-1F1EE","j":["flag","flag_cote_d_ivoire","banner","ivory","coast","country","nation"]},"flag-cook-islands":{"a":"Flag: Cook Islands","b":"1F1E8-1F1F0","j":["islands","cook","flag","banner","country","nation"]},"flag-chile":{"a":"Flag: Chile","b":"1F1E8-1F1F1","j":["flag","country","banner","nation"]},"flag-cameroon":{"a":"Flag: Cameroon","b":"1F1E8-1F1F2","j":["cm","flag","banner","country","nation"]},"flag-china":{"a":"Flag: China","b":"1F1E8-1F1F3","j":["china","flag","chinese","banner","country","prc","nation"]},"flag-colombia":{"a":"Flag: Colombia","b":"1F1E8-1F1F4","j":["co","flag","banner","country","nation"]},"flag-clipperton-island":{"a":"Flag: Clipperton Island","b":"1F1E8-1F1F5","j":["flag"]},"flag-costa-rica":{"a":"Flag: Costa Rica","b":"1F1E8-1F1F7","j":["rica","costa","flag","banner","country","nation"]},"flag-cuba":{"a":"Flag: Cuba","b":"1F1E8-1F1FA","j":["cu","flag","banner","country","nation"]},"flag-cape-verde":{"a":"Flag: Cape Verde","b":"1F1E8-1F1FB","j":["cabo","flag","banner","verde","country","nation"]},"flag-curaao":{"a":"Flag: Curaçao","b":"1F1E8-1F1FC","j":["flag_curacao","curaçao","flag","banner","country","nation"]},"flag-christmas-island":{"a":"Flag: Christmas Island","b":"1F1E8-1F1FD","j":["christmas","flag","banner","country","island","nation"]},"flag-cyprus":{"a":"Flag: Cyprus","b":"1F1E8-1F1FE","j":["flag","banner","country","nation","cy"]},"flag-czechia":{"a":"Flag: Czechia","b":"1F1E8-1F1FF","j":["cz","flag","banner","country","nation"]},"flag-germany":{"a":"Flag: Germany","b":"1F1E9-1F1EA","j":["country","flag","banner","german","nation"]},"flag-diego-garcia":{"a":"Flag: Diego Garcia","b":"1F1E9-1F1EC","j":["flag"]},"flag-djibouti":{"a":"Flag: Djibouti","b":"1F1E9-1F1EF","j":["dj","flag","banner","country","nation"]},"flag-denmark":{"a":"Flag: Denmark","b":"1F1E9-1F1F0","j":["flag","banner","dk","country","nation"]},"flag-dominica":{"a":"Flag: Dominica","b":"1F1E9-1F1F2","j":["flag","dm","banner","country","nation"]},"flag-dominican-republic":{"a":"Flag: Dominican Republic","b":"1F1E9-1F1F4","j":["flag","banner","republic","country","dominican","nation"]},"flag-algeria":{"a":"Flag: Algeria","b":"1F1E9-1F1FF","j":["flag","banner","dz","country","nation"]},"flag-ceuta--melilla":{"a":"Flag: Ceuta & Melilla","b":"1F1EA-1F1E6","j":["flag_ceuta_melilla","flag"]},"flag-ecuador":{"a":"Flag: Ecuador","b":"1F1EA-1F1E8","j":["nation","flag","banner","country","ec"]},"flag-estonia":{"a":"Flag: Estonia","b":"1F1EA-1F1EA","j":["ee","flag","banner","country","nation"]},"flag-egypt":{"a":"Flag: Egypt","b":"1F1EA-1F1EC","j":["flag","eg","banner","country","nation"]},"flag-western-sahara":{"a":"Flag: Western Sahara","b":"1F1EA-1F1ED","j":["sahara","flag","western","banner","country","nation"]},"flag-eritrea":{"a":"Flag: Eritrea","b":"1F1EA-1F1F7","j":["flag","banner","country","nation","er"]},"flag-spain":{"a":"Flag: Spain","b":"1F1EA-1F1F8","j":["spain","flag","banner","country","nation"]},"flag-ethiopia":{"a":"Flag: Ethiopia","b":"1F1EA-1F1F9","j":["et","flag","banner","country","nation"]},"flag-european-union":{"a":"Flag: European Union","b":"1F1EA-1F1FA","j":["european","flag","banner","union"]},"flag-finland":{"a":"Flag: Finland","b":"1F1EB-1F1EE","j":["flag","banner","fi","country","nation"]},"flag-fiji":{"a":"Flag: Fiji","b":"1F1EB-1F1EF","j":["flag","banner","fj","country","nation"]},"flag-falkland-islands":{"a":"Flag: Falkland Islands","b":"1F1EB-1F1F0","j":["islands","falkland","flag","banner","malvinas","country","nation"]},"flag-micronesia":{"a":"Flag: Micronesia","b":"1F1EB-1F1F2","j":["federated","country","states","flag","banner","micronesia","nation"]},"flag-faroe-islands":{"a":"Flag: Faroe Islands","b":"1F1EB-1F1F4","j":["faroe","islands","flag","banner","country","nation"]},"flag-france":{"a":"Flag: France","b":"1F1EB-1F1F7","j":["flag","banner","french","france","country","nation"]},"flag-gabon":{"a":"Flag: Gabon","b":"1F1EC-1F1E6","j":["flag","ga","banner","country","nation"]},"flag-united-kingdom":{"a":"Flag: United Kingdom","b":"1F1EC-1F1E7","j":["northern","great","british","UK","english","kingdom","united","flag","banner","england","britain","ireland","country","union jack","nation"]},"flag-grenada":{"a":"Flag: Grenada","b":"1F1EC-1F1E9","j":["gd","flag","banner","country","nation"]},"flag-georgia":{"a":"Flag: Georgia","b":"1F1EC-1F1EA","j":["ge","flag","banner","country","nation"]},"flag-french-guiana":{"a":"Flag: French Guiana","b":"1F1EC-1F1EB","j":["guiana","flag","banner","french","country","nation"]},"flag-guernsey":{"a":"Flag: Guernsey","b":"1F1EC-1F1EC","j":["gg","flag","banner","country","nation"]},"flag-ghana":{"a":"Flag: Ghana","b":"1F1EC-1F1ED","j":["gh","flag","banner","country","nation"]},"flag-gibraltar":{"a":"Flag: Gibraltar","b":"1F1EC-1F1EE","j":["flag","banner","country","nation","gi"]},"flag-greenland":{"a":"Flag: Greenland","b":"1F1EC-1F1F1","j":["gl","flag","banner","country","nation"]},"flag-gambia":{"a":"Flag: Gambia","b":"1F1EC-1F1F2","j":["gm","flag","banner","country","nation"]},"flag-guinea":{"a":"Flag: Guinea","b":"1F1EC-1F1F3","j":["gn","flag","banner","country","nation"]},"flag-guadeloupe":{"a":"Flag: Guadeloupe","b":"1F1EC-1F1F5","j":["gp","flag","banner","country","nation"]},"flag-equatorial-guinea":{"a":"Flag: Equatorial Guinea","b":"1F1EC-1F1F6","j":["equatorial","gn","flag","banner","country","nation"]},"flag-greece":{"a":"Flag: Greece","b":"1F1EC-1F1F7","j":["gr","flag","banner","country","nation"]},"flag-south-georgia--south-sandwich-islands":{"a":"Flag: South Georgia & South Sandwich Islands","b":"1F1EC-1F1F8","j":["georgia","islands","south","flag_south_georgia_south_sandwich_islands","flag","sandwich","banner","country","nation"]},"flag-guatemala":{"a":"Flag: Guatemala","b":"1F1EC-1F1F9","j":["flag","gt","banner","country","nation"]},"flag-guam":{"a":"Flag: Guam","b":"1F1EC-1F1FA","j":["gu","flag","banner","country","nation"]},"flag-guineabissau":{"a":"Flag: Guinea-Bissau","b":"1F1EC-1F1FC","j":["bissau","nation","gw","flag","banner","country","flag_guinea_bissau"]},"flag-guyana":{"a":"Flag: Guyana","b":"1F1EC-1F1FE","j":["gy","flag","banner","country","nation"]},"flag-hong-kong-sar-china":{"a":"Flag: Hong Kong Sar China","b":"1F1ED-1F1F0","j":["flag","banner","hong","kong","country","nation"]},"flag-heard--mcdonald-islands":{"a":"Flag: Heard & Mcdonald Islands","b":"1F1ED-1F1F2","j":["flag","flag_heard_mcdonald_islands"]},"flag-honduras":{"a":"Flag: Honduras","b":"1F1ED-1F1F3","j":["country","flag","banner","hn","nation"]},"flag-croatia":{"a":"Flag: Croatia","b":"1F1ED-1F1F7","j":["flag","banner","hr","country","nation"]},"flag-haiti":{"a":"Flag: Haiti","b":"1F1ED-1F1F9","j":["ht","flag","banner","country","nation"]},"flag-hungary":{"a":"Flag: Hungary","b":"1F1ED-1F1FA","j":["flag","banner","hu","country","nation"]},"flag-canary-islands":{"a":"Flag: Canary Islands","b":"1F1EE-1F1E8","j":["islands","canary","flag","banner","country","nation"]},"flag-indonesia":{"a":"Flag: Indonesia","b":"1F1EE-1F1E9","j":["flag","country","banner","nation"]},"flag-ireland":{"a":"Flag: Ireland","b":"1F1EE-1F1EA","j":["ie","flag","banner","country","nation"]},"flag-israel":{"a":"Flag: Israel","b":"1F1EE-1F1F1","j":["il","flag","banner","country","nation"]},"flag-isle-of-man":{"a":"Flag: Isle of Man","b":"1F1EE-1F1F2","j":["man","isle","flag","banner","country","nation"]},"flag-india":{"a":"Flag: India","b":"1F1EE-1F1F3","j":["country","flag","banner","in","nation"]},"flag-british-indian-ocean-territory":{"a":"Flag: British Indian Ocean Territory","b":"1F1EE-1F1F4","j":["british","ocean","flag","banner","territory","indian","country","nation"]},"flag-iraq":{"a":"Flag: Iraq","b":"1F1EE-1F1F6","j":["iq","flag","banner","country","nation"]},"flag-iran":{"a":"Flag: Iran","b":"1F1EE-1F1F7","j":["iran","islamic","flag","banner","republic","country","nation"]},"flag-iceland":{"a":"Flag: Iceland","b":"1F1EE-1F1F8","j":["flag","banner","country","is","nation"]},"flag-italy":{"a":"Flag: Italy","b":"1F1EE-1F1F9","j":["italy","flag","banner","country","nation"]},"flag-jersey":{"a":"Flag: Jersey","b":"1F1EF-1F1EA","j":["je","flag","banner","country","nation"]},"flag-jamaica":{"a":"Flag: Jamaica","b":"1F1EF-1F1F2","j":["flag","banner","jm","country","nation"]},"flag-jordan":{"a":"Flag: Jordan","b":"1F1EF-1F1F4","j":["jo","flag","banner","country","nation"]},"flag-japan":{"a":"Flag: Japan","b":"1F1EF-1F1F5","j":["flag","banner","japanese","country","nation"]},"flag-kenya":{"a":"Flag: Kenya","b":"1F1F0-1F1EA","j":["flag","banner","ke","country","nation"]},"flag-kyrgyzstan":{"a":"Flag: Kyrgyzstan","b":"1F1F0-1F1EC","j":["kg","flag","banner","country","nation"]},"flag-cambodia":{"a":"Flag: Cambodia","b":"1F1F0-1F1ED","j":["kh","flag","banner","country","nation"]},"flag-kiribati":{"a":"Flag: Kiribati","b":"1F1F0-1F1EE","j":["ki","flag","banner","country","nation"]},"flag-comoros":{"a":"Flag: Comoros","b":"1F1F0-1F1F2","j":["km","flag","banner","country","nation"]},"flag-st-kitts--nevis":{"a":"Flag: St. Kitts & Nevis","b":"1F1F0-1F1F3","j":["nation","nevis","flag","kitts","flag_st_kitts_nevis","banner","country","saint"]},"flag-north-korea":{"a":"Flag: North Korea","b":"1F1F0-1F1F5","j":["north","flag","banner","korea","country","nation"]},"flag-south-korea":{"a":"Flag: South Korea","b":"1F1F0-1F1F7","j":["south","flag","banner","korea","country","nation"]},"flag-kuwait":{"a":"Flag: Kuwait","b":"1F1F0-1F1FC","j":["kw","flag","banner","country","nation"]},"flag-cayman-islands":{"a":"Flag: Cayman Islands","b":"1F1F0-1F1FE","j":["islands","flag","banner","cayman","country","nation"]},"flag-kazakhstan":{"a":"Flag: Kazakhstan","b":"1F1F0-1F1FF","j":["kz","flag","banner","country","nation"]},"flag-laos":{"a":"Flag: Laos","b":"1F1F1-1F1E6","j":["democratic","flag","banner","lao","republic","country","nation"]},"flag-lebanon":{"a":"Flag: Lebanon","b":"1F1F1-1F1E7","j":["lb","flag","banner","country","nation"]},"flag-st-lucia":{"a":"Flag: St. Lucia","b":"1F1F1-1F1E8","j":["nation","saint","flag","banner","country","lucia"]},"flag-liechtenstein":{"a":"Flag: Liechtenstein","b":"1F1F1-1F1EE","j":["flag","li","banner","country","nation"]},"flag-sri-lanka":{"a":"Flag: Sri Lanka","b":"1F1F1-1F1F0","j":["lanka","sri","flag","banner","country","nation"]},"flag-liberia":{"a":"Flag: Liberia","b":"1F1F1-1F1F7","j":["flag","lr","banner","country","nation"]},"flag-lesotho":{"a":"Flag: Lesotho","b":"1F1F1-1F1F8","j":["ls","flag","banner","country","nation"]},"flag-lithuania":{"a":"Flag: Lithuania","b":"1F1F1-1F1F9","j":["flag","banner","country","nation","lt"]},"flag-luxembourg":{"a":"Flag: Luxembourg","b":"1F1F1-1F1FA","j":["lu","flag","banner","country","nation"]},"flag-latvia":{"a":"Flag: Latvia","b":"1F1F1-1F1FB","j":["flag","banner","lv","country","nation"]},"flag-libya":{"a":"Flag: Libya","b":"1F1F1-1F1FE","j":["ly","flag","banner","country","nation"]},"flag-morocco":{"a":"Flag: Morocco","b":"1F1F2-1F1E6","j":["ma","flag","banner","country","nation"]},"flag-monaco":{"a":"Flag: Monaco","b":"1F1F2-1F1E8","j":["mc","flag","banner","country","nation"]},"flag-moldova":{"a":"Flag: Moldova","b":"1F1F2-1F1E9","j":["flag","banner","republic","country","moldova","nation"]},"flag-montenegro":{"a":"Flag: Montenegro","b":"1F1F2-1F1EA","j":["me","flag","banner","country","nation"]},"flag-st-martin":{"a":"Flag: St. Martin","b":"1F1F2-1F1EB","j":["flag"]},"flag-madagascar":{"a":"Flag: Madagascar","b":"1F1F2-1F1EC","j":["mg","flag","banner","country","nation"]},"flag-marshall-islands":{"a":"Flag: Marshall Islands","b":"1F1F2-1F1ED","j":["islands","marshall","flag","banner","country","nation"]},"flag-north-macedonia":{"a":"Flag: North Macedonia","b":"1F1F2-1F1F0","j":["macedonia","flag","banner","country","nation"]},"flag-mali":{"a":"Flag: Mali","b":"1F1F2-1F1F1","j":["ml","flag","banner","country","nation"]},"flag-myanmar-burma":{"a":"Flag: Myanmar (Burma)","b":"1F1F2-1F1F2","j":["mm","country","flag","banner","flag_myanmar","nation"]},"flag-mongolia":{"a":"Flag: Mongolia","b":"1F1F2-1F1F3","j":["country","flag","banner","mn","nation"]},"flag-macao-sar-china":{"a":"Flag: Macao Sar China","b":"1F1F2-1F1F4","j":["flag","banner","country","nation","macao"]},"flag-northern-mariana-islands":{"a":"Flag: Northern Mariana Islands","b":"1F1F2-1F1F5","j":["northern","islands","flag","mariana","banner","country","nation"]},"flag-martinique":{"a":"Flag: Martinique","b":"1F1F2-1F1F6","j":["mq","flag","banner","country","nation"]},"flag-mauritania":{"a":"Flag: Mauritania","b":"1F1F2-1F1F7","j":["flag","banner","mr","country","nation"]},"flag-montserrat":{"a":"Flag: Montserrat","b":"1F1F2-1F1F8","j":["flag","banner","ms","country","nation"]},"flag-malta":{"a":"Flag: Malta","b":"1F1F2-1F1F9","j":["mt","flag","banner","country","nation"]},"flag-mauritius":{"a":"Flag: Mauritius","b":"1F1F2-1F1FA","j":["mu","flag","banner","country","nation"]},"flag-maldives":{"a":"Flag: Maldives","b":"1F1F2-1F1FB","j":["flag","banner","mv","country","nation"]},"flag-malawi":{"a":"Flag: Malawi","b":"1F1F2-1F1FC","j":["mw","flag","banner","country","nation"]},"flag-mexico":{"a":"Flag: Mexico","b":"1F1F2-1F1FD","j":["mx","flag","banner","country","nation"]},"flag-malaysia":{"a":"Flag: Malaysia","b":"1F1F2-1F1FE","j":["flag","my","banner","country","nation"]},"flag-mozambique":{"a":"Flag: Mozambique","b":"1F1F2-1F1FF","j":["flag","banner","mz","country","nation"]},"flag-namibia":{"a":"Flag: Namibia","b":"1F1F3-1F1E6","j":["na","flag","banner","country","nation"]},"flag-new-caledonia":{"a":"Flag: New Caledonia","b":"1F1F3-1F1E8","j":["country","flag","banner","caledonia","nation","new"]},"flag-niger":{"a":"Flag: Niger","b":"1F1F3-1F1EA","j":["flag","banner","ne","country","nation"]},"flag-norfolk-island":{"a":"Flag: Norfolk Island","b":"1F1F3-1F1EB","j":["norfolk","flag","banner","country","island","nation"]},"flag-nigeria":{"a":"Flag: Nigeria","b":"1F1F3-1F1EC","j":["flag","country","banner","nation"]},"flag-nicaragua":{"a":"Flag: Nicaragua","b":"1F1F3-1F1EE","j":["flag","banner","country","nation","ni"]},"flag-netherlands":{"a":"Flag: Netherlands","b":"1F1F3-1F1F1","j":["flag","banner","nl","country","nation"]},"flag-norway":{"a":"Flag: Norway","b":"1F1F3-1F1F4","j":["no","flag","banner","country","nation"]},"flag-nepal":{"a":"Flag: Nepal","b":"1F1F3-1F1F5","j":["flag","np","banner","country","nation"]},"flag-nauru":{"a":"Flag: Nauru","b":"1F1F3-1F1F7","j":["nr","flag","banner","country","nation"]},"flag-niue":{"a":"Flag: Niue","b":"1F1F3-1F1FA","j":["nu","flag","banner","country","nation"]},"flag-new-zealand":{"a":"Flag: New Zealand","b":"1F1F3-1F1FF","j":["flag","banner","country","zealand","nation","new"]},"flag-oman":{"a":"Flag: Oman","b":"1F1F4-1F1F2","j":["om_symbol","flag","banner","country","nation"]},"flag-panama":{"a":"Flag: Panama","b":"1F1F5-1F1E6","j":["flag","banner","country","nation","pa"]},"flag-peru":{"a":"Flag: Peru","b":"1F1F5-1F1EA","j":["flag","banner","pe","country","nation"]},"flag-french-polynesia":{"a":"Flag: French Polynesia","b":"1F1F5-1F1EB","j":["flag","banner","french","country","nation","polynesia"]},"flag-papua-new-guinea":{"a":"Flag: Papua New Guinea","b":"1F1F5-1F1EC","j":["flag","banner","guinea","country","nation","papua","new"]},"flag-philippines":{"a":"Flag: Philippines","b":"1F1F5-1F1ED","j":["ph","flag","banner","country","nation"]},"flag-pakistan":{"a":"Flag: Pakistan","b":"1F1F5-1F1F0","j":["pk","flag","banner","country","nation"]},"flag-poland":{"a":"Flag: Poland","b":"1F1F5-1F1F1","j":["flag","pl","banner","country","nation"]},"flag-st-pierre--miquelon":{"a":"Flag: St. Pierre & Miquelon","b":"1F1F5-1F1F2","j":["pierre","nation","flag_st_pierre_miquelon","flag","miquelon","banner","country","saint"]},"flag-pitcairn-islands":{"a":"Flag: Pitcairn Islands","b":"1F1F5-1F1F3","j":["country","flag","banner","pitcairn","nation"]},"flag-puerto-rico":{"a":"Flag: Puerto Rico","b":"1F1F5-1F1F7","j":["flag","banner","rico","country","puerto","nation"]},"flag-palestinian-territories":{"a":"Flag: Palestinian Territories","b":"1F1F5-1F1F8","j":["palestine","nation","territories","flag","banner","country","palestinian"]},"flag-portugal":{"a":"Flag: Portugal","b":"1F1F5-1F1F9","j":["flag","pt","banner","country","nation"]},"flag-palau":{"a":"Flag: Palau","b":"1F1F5-1F1FC","j":["flag","banner","pw","country","nation"]},"flag-paraguay":{"a":"Flag: Paraguay","b":"1F1F5-1F1FE","j":["py","flag","banner","country","nation"]},"flag-qatar":{"a":"Flag: Qatar","b":"1F1F6-1F1E6","j":["flag","qa","banner","country","nation"]},"flag-runion":{"a":"Flag: Réunion","b":"1F1F7-1F1EA","j":["flag_reunion","réunion","flag","banner","country","nation"]},"flag-romania":{"a":"Flag: Romania","b":"1F1F7-1F1F4","j":["ro","flag","banner","country","nation"]},"flag-serbia":{"a":"Flag: Serbia","b":"1F1F7-1F1F8","j":["flag","banner","rs","country","nation"]},"flag-russia":{"a":"Flag: Russia","b":"1F1F7-1F1FA","j":["russian","federation","flag","banner","country","nation"]},"flag-rwanda":{"a":"Flag: Rwanda","b":"1F1F7-1F1FC","j":["rw","flag","banner","country","nation"]},"flag-saudi-arabia":{"a":"Flag: Saudi Arabia","b":"1F1F8-1F1E6","j":["flag","country","banner","nation"]},"flag-solomon-islands":{"a":"Flag: Solomon Islands","b":"1F1F8-1F1E7","j":["islands","flag","banner","country","nation","solomon"]},"flag-seychelles":{"a":"Flag: Seychelles","b":"1F1F8-1F1E8","j":["flag","banner","sc","country","nation"]},"flag-sudan":{"a":"Flag: Sudan","b":"1F1F8-1F1E9","j":["sd","flag","banner","country","nation"]},"flag-sweden":{"a":"Flag: Sweden","b":"1F1F8-1F1EA","j":["flag","banner","country","nation","se"]},"flag-singapore":{"a":"Flag: Singapore","b":"1F1F8-1F1EC","j":["flag","banner","sg","country","nation"]},"flag-st-helena":{"a":"Flag: St. Helena","b":"1F1F8-1F1ED","j":["cunha","saint","flag","tristan","banner","helena","ascension","country","nation"]},"flag-slovenia":{"a":"Flag: Slovenia","b":"1F1F8-1F1EE","j":["flag","si","banner","country","nation"]},"flag-svalbard--jan-mayen":{"a":"Flag: Svalbard & Jan Mayen","b":"1F1F8-1F1EF","j":["flag_svalbard_jan_mayen","flag"]},"flag-slovakia":{"a":"Flag: Slovakia","b":"1F1F8-1F1F0","j":["flag","sk","banner","country","nation"]},"flag-sierra-leone":{"a":"Flag: Sierra Leone","b":"1F1F8-1F1F1","j":["sierra","leone","flag","banner","country","nation"]},"flag-san-marino":{"a":"Flag: San Marino","b":"1F1F8-1F1F2","j":["san","marino","flag","banner","country","nation"]},"flag-senegal":{"a":"Flag: Senegal","b":"1F1F8-1F1F3","j":["sn","flag","banner","country","nation"]},"flag-somalia":{"a":"Flag: Somalia","b":"1F1F8-1F1F4","j":["flag","banner","so","country","nation"]},"flag-suriname":{"a":"Flag: Suriname","b":"1F1F8-1F1F7","j":["sr","flag","banner","country","nation"]},"flag-south-sudan":{"a":"Flag: South Sudan","b":"1F1F8-1F1F8","j":["sd","south","flag","banner","country","nation"]},"flag-so-tom--prncipe":{"a":"Flag: São Tomé & Príncipe","b":"1F1F8-1F1F9","j":["principe","tome","flag","flag_sao_tome_principe","banner","country","nation","sao"]},"flag-el-salvador":{"a":"Flag: El Salvador","b":"1F1F8-1F1FB","j":["el","flag","banner","salvador","country","nation"]},"flag-sint-maarten":{"a":"Flag: Sint Maarten","b":"1F1F8-1F1FD","j":["maarten","sint","dutch","flag","banner","country","nation"]},"flag-syria":{"a":"Flag: Syria","b":"1F1F8-1F1FE","j":["syrian","flag","banner","arab","republic","country","nation"]},"flag-eswatini":{"a":"Flag: Eswatini","b":"1F1F8-1F1FF","j":["sz","flag","banner","country","nation"]},"flag-tristan-da-cunha":{"a":"Flag: Tristan Da Cunha","b":"1F1F9-1F1E6","j":["flag"]},"flag-turks--caicos-islands":{"a":"Flag: Turks & Caicos Islands","b":"1F1F9-1F1E8","j":["islands","turks","flag","flag_turks_caicos_islands","banner","caicos","country","nation"]},"flag-chad":{"a":"Flag: Chad","b":"1F1F9-1F1E9","j":["td","flag","banner","country","nation"]},"flag-french-southern-territories":{"a":"Flag: French Southern Territories","b":"1F1F9-1F1EB","j":["territories","southern","flag","banner","french","country","nation"]},"flag-togo":{"a":"Flag: Togo","b":"1F1F9-1F1EC","j":["tg","flag","banner","country","nation"]},"flag-thailand":{"a":"Flag: Thailand","b":"1F1F9-1F1ED","j":["flag","banner","th","country","nation"]},"flag-tajikistan":{"a":"Flag: Tajikistan","b":"1F1F9-1F1EF","j":["tj","flag","banner","country","nation"]},"flag-tokelau":{"a":"Flag: Tokelau","b":"1F1F9-1F1F0","j":["tk","flag","banner","country","nation"]},"flag-timorleste":{"a":"Flag: Timor-Leste","b":"1F1F9-1F1F1","j":["timor","flag_timor_leste","flag","banner","leste","country","nation"]},"flag-turkmenistan":{"a":"Flag: Turkmenistan","b":"1F1F9-1F1F2","j":["flag","country","banner","nation"]},"flag-tunisia":{"a":"Flag: Tunisia","b":"1F1F9-1F1F3","j":["tn","flag","banner","country","nation"]},"flag-tonga":{"a":"Flag: Tonga","b":"1F1F9-1F1F4","j":["to","flag","banner","country","nation"]},"flag-turkey":{"a":"Flag: Turkey","b":"1F1F9-1F1F7","j":["turkey","flag","banner","country","nation"]},"flag-trinidad--tobago":{"a":"Flag: Trinidad & Tobago","b":"1F1F9-1F1F9","j":["flag_trinidad_tobago","flag","banner","trinidad","tobago","country","nation"]},"flag-tuvalu":{"a":"Flag: Tuvalu","b":"1F1F9-1F1FB","j":["flag","country","banner","nation"]},"flag-taiwan":{"a":"Flag: Taiwan","b":"1F1F9-1F1FC","j":["tw","flag","banner","country","nation"]},"flag-tanzania":{"a":"Flag: Tanzania","b":"1F1F9-1F1FF","j":["tanzania","united","flag","banner","republic","country","nation"]},"flag-ukraine":{"a":"Flag: Ukraine","b":"1F1FA-1F1E6","j":["flag","banner","country","nation","ua"]},"flag-uganda":{"a":"Flag: Uganda","b":"1F1FA-1F1EC","j":["ug","flag","banner","country","nation"]},"flag-us-outlying-islands":{"a":"Flag: U.S. Outlying Islands","b":"1F1FA-1F1F2","j":["flag","flag_u_s_outlying_islands"]},"flag-united-nations":{"a":"Flag: United Nations","b":"1F1FA-1F1F3","j":["flag","un","banner"]},"flag-united-states":{"a":"Flag: United States","b":"1F1FA-1F1F8","j":["america","united","flag","states","banner","country","nation"]},"flag-uruguay":{"a":"Flag: Uruguay","b":"1F1FA-1F1FE","j":["flag","uy","banner","country","nation"]},"flag-uzbekistan":{"a":"Flag: Uzbekistan","b":"1F1FA-1F1FF","j":["uz","flag","banner","country","nation"]},"flag-vatican-city":{"a":"Flag: Vatican City","b":"1F1FB-1F1E6","j":["vatican","city","flag","banner","country","nation"]},"flag-st-vincent--grenadines":{"a":"Flag: St. Vincent & Grenadines","b":"1F1FB-1F1E8","j":["nation","flag_st_vincent_grenadines","vincent","grenadines","flag","banner","country","saint"]},"flag-venezuela":{"a":"Flag: Venezuela","b":"1F1FB-1F1EA","j":["bolivarian","ve","flag","banner","republic","country","nation"]},"flag-british-virgin-islands":{"a":"Flag: British Virgin Islands","b":"1F1FB-1F1EC","j":["virgin","bvi","islands","british","flag","banner","country","nation"]},"flag-us-virgin-islands":{"a":"Flag: U.S. Virgin Islands","b":"1F1FB-1F1EE","j":["virgin","islands","us","flag","banner","country","nation","flag_u_s_virgin_islands"]},"flag-vietnam":{"a":"Flag: Vietnam","b":"1F1FB-1F1F3","j":["nam","flag","banner","viet","country","nation"]},"flag-vanuatu":{"a":"Flag: Vanuatu","b":"1F1FB-1F1FA","j":["flag","banner","vu","country","nation"]},"flag-wallis--futuna":{"a":"Flag: Wallis & Futuna","b":"1F1FC-1F1EB","j":["flag_wallis_futuna","flag","wallis","futuna","banner","country","nation"]},"flag-samoa":{"a":"Flag: Samoa","b":"1F1FC-1F1F8","j":["ws","flag","banner","country","nation"]},"flag-kosovo":{"a":"Flag: Kosovo","b":"1F1FD-1F1F0","j":["flag","banner","country","nation","xk"]},"flag-yemen":{"a":"Flag: Yemen","b":"1F1FE-1F1EA","j":["flag","banner","ye","country","nation"]},"flag-mayotte":{"a":"Flag: Mayotte","b":"1F1FE-1F1F9","j":["flag","banner","country","yt","nation"]},"flag-south-africa":{"a":"Flag: South Africa","b":"1F1FF-1F1E6","j":["south","africa","flag","banner","country","nation"]},"flag-zambia":{"a":"Flag: Zambia","b":"1F1FF-1F1F2","j":["zm","flag","banner","country","nation"]},"flag-zimbabwe":{"a":"Flag: Zimbabwe","b":"1F1FF-1F1FC","j":["zw","flag","banner","country","nation"]},"flag-england":{"a":"Flag: England","b":"1F3F4-E0067-E0062-E0065-E006E-E0067-E007F","j":["flag","english"]},"flag-scotland":{"a":"Flag: Scotland","b":"1F3F4-E0067-E0062-E0073-E0063-E0074-E007F","j":["scottish","flag"]},"flag-wales":{"a":"Flag: Wales","b":"1F3F4-E0067-E0062-E0077-E006C-E0073-E007F","j":["flag","welsh"]}},"aliases":{}} \ No newline at end of file +{"compressed":true,"categories":[{"id":"smileys_&_emotion","name":"Smileys & Emotion","emojis":["grinning-face","grinning-face-with-big-eyes","grinning-face-with-smiling-eyes","beaming-face-with-smiling-eyes","grinning-squinting-face","grinning-face-with-sweat","rolling-on-the-floor-laughing","face-with-tears-of-joy","slightly-smiling-face","upsidedown-face","winking-face","smiling-face-with-smiling-eyes","smiling-face-with-halo","smiling-face-with-hearts","smiling-face-with-hearteyes","starstruck","face-blowing-a-kiss","kissing-face","smiling-face","kissing-face-with-closed-eyes","kissing-face-with-smiling-eyes","smiling-face-with-tear","face-savoring-food","face-with-tongue","winking-face-with-tongue","zany-face","squinting-face-with-tongue","moneymouth-face","hugging-face","face-with-hand-over-mouth","shushing-face","thinking-face","zippermouth-face","face-with-raised-eyebrow","neutral-face","expressionless-face","face-without-mouth","face-in-clouds","smirking-face","unamused-face","face-with-rolling-eyes","grimacing-face","face-exhaling","lying-face","relieved-face","pensive-face","sleepy-face","drooling-face","sleeping-face","face-with-medical-mask","face-with-thermometer","face-with-headbandage","nauseated-face","face-vomiting","sneezing-face","hot-face","cold-face","woozy-face","knockedout-face","face-with-spiral-eyes","exploding-head","cowboy-hat-face","partying-face","disguised-face","smiling-face-with-sunglasses","nerd-face","face-with-monocle","confused-face","worried-face","slightly-frowning-face","frowning-face","face-with-open-mouth","hushed-face","astonished-face","flushed-face","pleading-face","frowning-face-with-open-mouth","anguished-face","fearful-face","anxious-face-with-sweat","sad-but-relieved-face","crying-face","loudly-crying-face","face-screaming-in-fear","confounded-face","persevering-face","disappointed-face","downcast-face-with-sweat","weary-face","tired-face","yawning-face","face-with-steam-from-nose","pouting-face","angry-face","face-with-symbols-on-mouth","smiling-face-with-horns","angry-face-with-horns","skull","skull-and-crossbones","pile-of-poo","clown-face","ogre","goblin","ghost","alien","alien-monster","robot","grinning-cat","grinning-cat-with-smiling-eyes","cat-with-tears-of-joy","smiling-cat-with-hearteyes","cat-with-wry-smile","kissing-cat","weary-cat","crying-cat","pouting-cat","seenoevil-monkey","hearnoevil-monkey","speaknoevil-monkey","kiss-mark","love-letter","heart-with-arrow","heart-with-ribbon","sparkling-heart","growing-heart","beating-heart","revolving-hearts","two-hearts","heart-decoration","heart-exclamation","broken-heart","heart-on-fire","mending-heart","red-heart","orange-heart","yellow-heart","green-heart","blue-heart","purple-heart","brown-heart","black-heart","white-heart","hundred-points","anger-symbol","collision","dizzy","sweat-droplets","dashing-away","hole","bomb","speech-balloon","eye-in-speech-bubble","left-speech-bubble","right-anger-bubble","thought-balloon","zzz"]},{"id":"people_&_body","name":"People & Body","emojis":["waving-hand","raised-back-of-hand","hand-with-fingers-splayed","raised-hand","vulcan-salute","ok-hand","pinched-fingers","pinching-hand","victory-hand","crossed-fingers","loveyou-gesture","sign-of-the-horns","call-me-hand","backhand-index-pointing-left","backhand-index-pointing-right","backhand-index-pointing-up","middle-finger","backhand-index-pointing-down","index-pointing-up","thumbs-up","thumbs-down","raised-fist","oncoming-fist","leftfacing-fist","rightfacing-fist","clapping-hands","raising-hands","open-hands","palms-up-together","handshake","folded-hands","writing-hand","nail-polish","selfie","flexed-biceps","mechanical-arm","mechanical-leg","leg","foot","ear","ear-with-hearing-aid","nose","brain","anatomical-heart","lungs","tooth","bone","eyes","eye","tongue","mouth","baby","child","boy","girl","person","person-blond-hair","man","person-beard","man-beard","woman-beard","man-red-hair","man-curly-hair","man-white-hair","man-bald","woman","woman-red-hair","person-red-hair","woman-curly-hair","person-curly-hair","woman-white-hair","person-white-hair","woman-bald","person-bald","woman-blond-hair","man-blond-hair","older-person","old-man","old-woman","person-frowning","man-frowning","woman-frowning","person-pouting","man-pouting","woman-pouting","person-gesturing-no","man-gesturing-no","woman-gesturing-no","person-gesturing-ok","man-gesturing-ok","woman-gesturing-ok","person-tipping-hand","man-tipping-hand","woman-tipping-hand","person-raising-hand","man-raising-hand","woman-raising-hand","deaf-person","deaf-man","deaf-woman","person-bowing","man-bowing","woman-bowing","person-facepalming","man-facepalming","woman-facepalming","person-shrugging","man-shrugging","woman-shrugging","health-worker","man-health-worker","woman-health-worker","student","man-student","woman-student","teacher","man-teacher","woman-teacher","judge","man-judge","woman-judge","farmer","man-farmer","woman-farmer","cook","man-cook","woman-cook","mechanic","man-mechanic","woman-mechanic","factory-worker","man-factory-worker","woman-factory-worker","office-worker","man-office-worker","woman-office-worker","scientist","man-scientist","woman-scientist","technologist","man-technologist","woman-technologist","singer","man-singer","woman-singer","artist","man-artist","woman-artist","pilot","man-pilot","woman-pilot","astronaut","man-astronaut","woman-astronaut","firefighter","man-firefighter","woman-firefighter","police-officer","man-police-officer","woman-police-officer","detective","man-detective","woman-detective","guard","man-guard","woman-guard","ninja","construction-worker","man-construction-worker","woman-construction-worker","prince","princess","person-wearing-turban","man-wearing-turban","woman-wearing-turban","person-with-skullcap","woman-with-headscarf","person-in-tuxedo","man-in-tuxedo","woman-in-tuxedo","person-with-veil","man-with-veil","woman-with-veil","pregnant-woman","breastfeeding","woman-feeding-baby","man-feeding-baby","person-feeding-baby","baby-angel","santa-claus","mrs-claus","mx-claus","superhero","man-superhero","woman-superhero","supervillain","man-supervillain","woman-supervillain","mage","man-mage","woman-mage","fairy","man-fairy","woman-fairy","vampire","man-vampire","woman-vampire","merperson","merman","mermaid","elf","man-elf","woman-elf","genie","man-genie","woman-genie","zombie","man-zombie","woman-zombie","person-getting-massage","man-getting-massage","woman-getting-massage","person-getting-haircut","man-getting-haircut","woman-getting-haircut","person-walking","man-walking","woman-walking","person-standing","man-standing","woman-standing","person-kneeling","man-kneeling","woman-kneeling","person-with-white-cane","man-with-white-cane","woman-with-white-cane","person-in-motorized-wheelchair","man-in-motorized-wheelchair","woman-in-motorized-wheelchair","person-in-manual-wheelchair","man-in-manual-wheelchair","woman-in-manual-wheelchair","person-running","man-running","woman-running","woman-dancing","man-dancing","person-in-suit-levitating","people-with-bunny-ears","men-with-bunny-ears","women-with-bunny-ears","person-in-steamy-room","man-in-steamy-room","woman-in-steamy-room","person-climbing","man-climbing","woman-climbing","person-fencing","horse-racing","skier","snowboarder","person-golfing","man-golfing","woman-golfing","person-surfing","man-surfing","woman-surfing","person-rowing-boat","man-rowing-boat","woman-rowing-boat","person-swimming","man-swimming","woman-swimming","person-bouncing-ball","man-bouncing-ball","woman-bouncing-ball","person-lifting-weights","man-lifting-weights","woman-lifting-weights","person-biking","man-biking","woman-biking","person-mountain-biking","man-mountain-biking","woman-mountain-biking","person-cartwheeling","man-cartwheeling","woman-cartwheeling","people-wrestling","men-wrestling","women-wrestling","person-playing-water-polo","man-playing-water-polo","woman-playing-water-polo","person-playing-handball","man-playing-handball","woman-playing-handball","person-juggling","man-juggling","woman-juggling","person-in-lotus-position","man-in-lotus-position","woman-in-lotus-position","person-taking-bath","person-in-bed","people-holding-hands","women-holding-hands","woman-and-man-holding-hands","men-holding-hands","kiss","kiss-woman-man","kiss-man-man","kiss-woman-woman","couple-with-heart","couple-with-heart-woman-man","couple-with-heart-man-man","couple-with-heart-woman-woman","family","family-man-woman-boy","family-man-woman-girl","family-man-woman-girl-boy","family-man-woman-boy-boy","family-man-woman-girl-girl","family-man-man-boy","family-man-man-girl","family-man-man-girl-boy","family-man-man-boy-boy","family-man-man-girl-girl","family-woman-woman-boy","family-woman-woman-girl","family-woman-woman-girl-boy","family-woman-woman-boy-boy","family-woman-woman-girl-girl","family-man-boy","family-man-boy-boy","family-man-girl","family-man-girl-boy","family-man-girl-girl","family-woman-boy","family-woman-boy-boy","family-woman-girl","family-woman-girl-boy","family-woman-girl-girl","speaking-head","bust-in-silhouette","busts-in-silhouette","people-hugging","footprints"]},{"id":"animals_&_nature","name":"Animals & Nature","emojis":["monkey-face","monkey","gorilla","orangutan","dog-face","dog","guide-dog","service-dog","poodle","wolf","fox","raccoon","cat-face","cat","black-cat","lion","tiger-face","tiger","leopard","horse-face","horse","unicorn","zebra","deer","bison","cow-face","ox","water-buffalo","cow","pig-face","pig","boar","pig-nose","ram","ewe","goat","camel","twohump-camel","llama","giraffe","elephant","mammoth","rhinoceros","hippopotamus","mouse-face","mouse","rat","hamster","rabbit-face","rabbit","chipmunk","beaver","hedgehog","bat","bear","polar-bear","koala","panda","sloth","otter","skunk","kangaroo","badger","paw-prints","turkey","chicken","rooster","hatching-chick","baby-chick","frontfacing-baby-chick","bird","penguin","dove","eagle","duck","swan","owl","dodo","feather","flamingo","peacock","parrot","frog","crocodile","turtle","lizard","snake","dragon-face","dragon","sauropod","trex","spouting-whale","whale","dolphin","seal","fish","tropical-fish","blowfish","shark","octopus","spiral-shell","snail","butterfly","bug","ant","honeybee","beetle","lady-beetle","cricket","cockroach","spider","spider-web","scorpion","mosquito","fly","worm","microbe","bouquet","cherry-blossom","white-flower","rosette","rose","wilted-flower","hibiscus","sunflower","blossom","tulip","seedling","potted-plant","evergreen-tree","deciduous-tree","palm-tree","cactus","sheaf-of-rice","herb","shamrock","four-leaf-clover","maple-leaf","fallen-leaf","leaf-fluttering-in-wind"]},{"id":"food_&_drink","name":"Food & Drink","emojis":["grapes","melon","watermelon","tangerine","lemon","banana","pineapple","mango","red-apple","green-apple","pear","peach","cherries","strawberry","blueberries","kiwi-fruit","tomato","olive","coconut","avocado","eggplant","potato","carrot","ear-of-corn","hot-pepper","bell-pepper","cucumber","leafy-green","broccoli","garlic","onion","mushroom","peanuts","chestnut","bread","croissant","baguette-bread","flatbread","pretzel","bagel","pancakes","waffle","cheese-wedge","meat-on-bone","poultry-leg","cut-of-meat","bacon","hamburger","french-fries","pizza","hot-dog","sandwich","taco","burrito","tamale","stuffed-flatbread","falafel","egg","cooking","shallow-pan-of-food","pot-of-food","fondue","bowl-with-spoon","green-salad","popcorn","butter","salt","canned-food","bento-box","rice-cracker","rice-ball","cooked-rice","curry-rice","steaming-bowl","spaghetti","roasted-sweet-potato","oden","sushi","fried-shrimp","fish-cake-with-swirl","moon-cake","dango","dumpling","fortune-cookie","takeout-box","crab","lobster","shrimp","squid","oyster","soft-ice-cream","shaved-ice","ice-cream","doughnut","cookie","birthday-cake","shortcake","cupcake","pie","chocolate-bar","candy","lollipop","custard","honey-pot","baby-bottle","glass-of-milk","hot-beverage","teapot","teacup-without-handle","sake","bottle-with-popping-cork","wine-glass","cocktail-glass","tropical-drink","beer-mug","clinking-beer-mugs","clinking-glasses","tumbler-glass","cup-with-straw","bubble-tea","beverage-box","mate","ice","chopsticks","fork-and-knife-with-plate","fork-and-knife","spoon","kitchen-knife","amphora"]},{"id":"travel_&_places","name":"Travel & Places","emojis":["globe-showing-europeafrica","globe-showing-americas","globe-showing-asiaaustralia","globe-with-meridians","world-map","map-of-japan","compass","snowcapped-mountain","mountain","volcano","mount-fuji","camping","beach-with-umbrella","desert","desert-island","national-park","stadium","classical-building","building-construction","brick","rock","wood","hut","houses","derelict-house","house","house-with-garden","office-building","japanese-post-office","post-office","hospital","bank","hotel","love-hotel","convenience-store","school","department-store","factory","japanese-castle","castle","wedding","tokyo-tower","statue-of-liberty","church","mosque","hindu-temple","synagogue","shinto-shrine","kaaba","fountain","tent","foggy","night-with-stars","cityscape","sunrise-over-mountains","sunrise","cityscape-at-dusk","sunset","bridge-at-night","hot-springs","carousel-horse","ferris-wheel","roller-coaster","barber-pole","circus-tent","locomotive","railway-car","highspeed-train","bullet-train","train","metro","light-rail","station","tram","monorail","mountain-railway","tram-car","bus","oncoming-bus","trolleybus","minibus","ambulance","fire-engine","police-car","oncoming-police-car","taxi","oncoming-taxi","automobile","oncoming-automobile","sport-utility-vehicle","pickup-truck","delivery-truck","articulated-lorry","tractor","racing-car","motorcycle","motor-scooter","manual-wheelchair","motorized-wheelchair","auto-rickshaw","bicycle","kick-scooter","skateboard","roller-skate","bus-stop","motorway","railway-track","oil-drum","fuel-pump","police-car-light","horizontal-traffic-light","vertical-traffic-light","stop-sign","construction","anchor","sailboat","canoe","speedboat","passenger-ship","ferry","motor-boat","ship","airplane","small-airplane","airplane-departure","airplane-arrival","parachute","seat","helicopter","suspension-railway","mountain-cableway","aerial-tramway","satellite","rocket","flying-saucer","bellhop-bell","luggage","hourglass-done","hourglass-not-done","watch","alarm-clock","stopwatch","timer-clock","mantelpiece-clock","twelve-oclock","twelvethirty","one-oclock","onethirty","two-oclock","twothirty","three-oclock","threethirty","four-oclock","fourthirty","five-oclock","fivethirty","six-oclock","sixthirty","seven-oclock","seventhirty","eight-oclock","eightthirty","nine-oclock","ninethirty","ten-oclock","tenthirty","eleven-oclock","eleventhirty","new-moon","waxing-crescent-moon","first-quarter-moon","waxing-gibbous-moon","full-moon","waning-gibbous-moon","last-quarter-moon","waning-crescent-moon","crescent-moon","new-moon-face","first-quarter-moon-face","last-quarter-moon-face","thermometer","sun","full-moon-face","sun-with-face","ringed-planet","star","glowing-star","shooting-star","milky-way","cloud","sun-behind-cloud","cloud-with-lightning-and-rain","sun-behind-small-cloud","sun-behind-large-cloud","sun-behind-rain-cloud","cloud-with-rain","cloud-with-snow","cloud-with-lightning","tornado","fog","wind-face","cyclone","rainbow","closed-umbrella","umbrella","umbrella-with-rain-drops","umbrella-on-ground","high-voltage","snowflake","snowman","snowman-without-snow","comet","fire","droplet","water-wave"]},{"id":"activities","name":"Activities","emojis":["jackolantern","christmas-tree","fireworks","sparkler","firecracker","sparkles","balloon","party-popper","confetti-ball","tanabata-tree","pine-decoration","japanese-dolls","carp-streamer","wind-chime","moon-viewing-ceremony","red-envelope","ribbon","wrapped-gift","reminder-ribbon","admission-tickets","ticket","military-medal","trophy","sports-medal","1st-place-medal","2nd-place-medal","3rd-place-medal","soccer-ball","baseball","softball","basketball","volleyball","american-football","rugby-football","tennis","flying-disc","bowling","cricket-game","field-hockey","ice-hockey","lacrosse","ping-pong","badminton","boxing-glove","martial-arts-uniform","goal-net","flag-in-hole","ice-skate","fishing-pole","diving-mask","running-shirt","skis","sled","curling-stone","bullseye","yoyo","kite","pool-8-ball","crystal-ball","magic-wand","nazar-amulet","video-game","joystick","slot-machine","game-die","puzzle-piece","teddy-bear","piata","nesting-dolls","spade-suit","heart-suit","diamond-suit","club-suit","chess-pawn","joker","mahjong-red-dragon","flower-playing-cards","performing-arts","framed-picture","artist-palette","thread","sewing-needle","yarn","knot"]},{"id":"objects","name":"Objects","emojis":["glasses","sunglasses","goggles","lab-coat","safety-vest","necktie","tshirt","jeans","scarf","gloves","coat","socks","dress","kimono","sari","onepiece-swimsuit","briefs","shorts","bikini","womans-clothes","purse","handbag","clutch-bag","shopping-bags","backpack","thong-sandal","mans-shoe","running-shoe","hiking-boot","flat-shoe","highheeled-shoe","womans-sandal","ballet-shoes","womans-boot","crown","womans-hat","top-hat","graduation-cap","billed-cap","military-helmet","rescue-workers-helmet","prayer-beads","lipstick","ring","gem-stone","muted-speaker","speaker-low-volume","speaker-medium-volume","speaker-high-volume","loudspeaker","megaphone","postal-horn","bell","bell-with-slash","musical-score","musical-note","musical-notes","studio-microphone","level-slider","control-knobs","microphone","headphone","radio","saxophone","accordion","guitar","musical-keyboard","trumpet","violin","banjo","drum","long-drum","mobile-phone","mobile-phone-with-arrow","telephone","telephone-receiver","pager","fax-machine","battery","electric-plug","laptop","desktop-computer","printer","keyboard","computer-mouse","trackball","computer-disk","floppy-disk","optical-disk","dvd","abacus","movie-camera","film-frames","film-projector","clapper-board","television","camera","camera-with-flash","video-camera","videocassette","magnifying-glass-tilted-left","magnifying-glass-tilted-right","candle","light-bulb","flashlight","red-paper-lantern","diya-lamp","notebook-with-decorative-cover","closed-book","open-book","green-book","blue-book","orange-book","books","notebook","ledger","page-with-curl","scroll","page-facing-up","newspaper","rolledup-newspaper","bookmark-tabs","bookmark","label","money-bag","coin","yen-banknote","dollar-banknote","euro-banknote","pound-banknote","money-with-wings","credit-card","receipt","chart-increasing-with-yen","envelope","email","incoming-envelope","envelope-with-arrow","outbox-tray","inbox-tray","package","closed-mailbox-with-raised-flag","closed-mailbox-with-lowered-flag","open-mailbox-with-raised-flag","open-mailbox-with-lowered-flag","postbox","ballot-box-with-ballot","pencil","black-nib","fountain-pen","pen","paintbrush","crayon","memo","briefcase","file-folder","open-file-folder","card-index-dividers","calendar","tearoff-calendar","spiral-notepad","spiral-calendar","card-index","chart-increasing","chart-decreasing","bar-chart","clipboard","pushpin","round-pushpin","paperclip","linked-paperclips","straight-ruler","triangular-ruler","scissors","card-file-box","file-cabinet","wastebasket","locked","unlocked","locked-with-pen","locked-with-key","key","old-key","hammer","axe","pick","hammer-and-pick","hammer-and-wrench","dagger","crossed-swords","water-pistol","boomerang","bow-and-arrow","shield","carpentry-saw","wrench","screwdriver","nut-and-bolt","gear","clamp","balance-scale","white-cane","link","chains","hook","toolbox","magnet","ladder","alembic","test-tube","petri-dish","dna","microscope","telescope","satellite-antenna","syringe","drop-of-blood","pill","adhesive-bandage","stethoscope","door","elevator","mirror","window","bed","couch-and-lamp","chair","toilet","plunger","shower","bathtub","mouse-trap","razor","lotion-bottle","safety-pin","broom","basket","roll-of-paper","bucket","soap","toothbrush","sponge","fire-extinguisher","shopping-cart","cigarette","coffin","headstone","funeral-urn","moai","placard"]},{"id":"symbols","name":"Symbols","emojis":["atm-sign","litter-in-bin-sign","potable-water","wheelchair-symbol","mens-room","womens-room","restroom","baby-symbol","water-closet","passport-control","customs","baggage-claim","left-luggage","warning","children-crossing","no-entry","prohibited","no-bicycles","no-smoking","no-littering","nonpotable-water","no-pedestrians","no-mobile-phones","no-one-under-eighteen","radioactive","biohazard","up-arrow","upright-arrow","right-arrow","downright-arrow","down-arrow","downleft-arrow","left-arrow","upleft-arrow","updown-arrow","leftright-arrow","right-arrow-curving-left","left-arrow-curving-right","right-arrow-curving-up","right-arrow-curving-down","clockwise-vertical-arrows","counterclockwise-arrows-button","back-arrow","end-arrow","on-arrow","soon-arrow","top-arrow","place-of-worship","atom-symbol","om","star-of-david","wheel-of-dharma","yin-yang","latin-cross","orthodox-cross","star-and-crescent","peace-symbol","menorah","dotted-sixpointed-star","aries","taurus","gemini","cancer","leo","virgo","libra","scorpio","sagittarius","capricorn","aquarius","pisces","ophiuchus","shuffle-tracks-button","repeat-button","repeat-single-button","play-button","fastforward-button","next-track-button","play-or-pause-button","reverse-button","fast-reverse-button","last-track-button","upwards-button","fast-up-button","downwards-button","fast-down-button","pause-button","stop-button","record-button","eject-button","cinema","dim-button","bright-button","antenna-bars","vibration-mode","mobile-phone-off","female-sign","male-sign","transgender-symbol","multiply","plus","minus","divide","infinity","double-exclamation-mark","exclamation-question-mark","red-question-mark","white-question-mark","white-exclamation-mark","red-exclamation-mark","wavy-dash","currency-exchange","heavy-dollar-sign","medical-symbol","recycling-symbol","fleurdelis","trident-emblem","name-badge","japanese-symbol-for-beginner","hollow-red-circle","check-mark-button","check-box-with-check","check-mark","cross-mark","cross-mark-button","curly-loop","double-curly-loop","part-alternation-mark","eightspoked-asterisk","eightpointed-star","sparkle","copyright","registered","trade-mark","keycap","keycap","keycap-0","keycap-1","keycap-2","keycap-3","keycap-4","keycap-5","keycap-6","keycap-7","keycap-8","keycap-9","keycap-10","input-latin-uppercase","input-latin-lowercase","input-numbers","input-symbols","input-latin-letters","a-button-blood-type","ab-button-blood-type","b-button-blood-type","cl-button","cool-button","free-button","information","id-button","circled-m","new-button","ng-button","o-button-blood-type","ok-button","p-button","sos-button","up-button","vs-button","japanese-here-button","japanese-service-charge-button","japanese-monthly-amount-button","japanese-not-free-of-charge-button","japanese-reserved-button","japanese-bargain-button","japanese-discount-button","japanese-free-of-charge-button","japanese-prohibited-button","japanese-acceptable-button","japanese-application-button","japanese-passing-grade-button","japanese-vacancy-button","japanese-congratulations-button","japanese-secret-button","japanese-open-for-business-button","japanese-no-vacancy-button","red-circle","orange-circle","yellow-circle","green-circle","blue-circle","purple-circle","brown-circle","black-circle","white-circle","red-square","orange-square","yellow-square","green-square","blue-square","purple-square","brown-square","black-large-square","white-large-square","black-medium-square","white-medium-square","black-mediumsmall-square","white-mediumsmall-square","black-small-square","white-small-square","large-orange-diamond","large-blue-diamond","small-orange-diamond","small-blue-diamond","red-triangle-pointed-up","red-triangle-pointed-down","diamond-with-a-dot","radio-button","white-square-button","black-square-button"]},{"id":"flags","name":"Flags","emojis":["chequered-flag","triangular-flag","crossed-flags","black-flag","white-flag","rainbow-flag","transgender-flag","pirate-flag","flag-ascension-island","flag-andorra","flag-united-arab-emirates","flag-afghanistan","flag-antigua--barbuda","flag-anguilla","flag-albania","flag-armenia","flag-angola","flag-antarctica","flag-argentina","flag-american-samoa","flag-austria","flag-australia","flag-aruba","flag-land-islands","flag-azerbaijan","flag-bosnia--herzegovina","flag-barbados","flag-bangladesh","flag-belgium","flag-burkina-faso","flag-bulgaria","flag-bahrain","flag-burundi","flag-benin","flag-st-barthlemy","flag-bermuda","flag-brunei","flag-bolivia","flag-caribbean-netherlands","flag-brazil","flag-bahamas","flag-bhutan","flag-bouvet-island","flag-botswana","flag-belarus","flag-belize","flag-canada","flag-cocos-keeling-islands","flag-congo--kinshasa","flag-central-african-republic","flag-congo--brazzaville","flag-switzerland","flag-cte-divoire","flag-cook-islands","flag-chile","flag-cameroon","flag-china","flag-colombia","flag-clipperton-island","flag-costa-rica","flag-cuba","flag-cape-verde","flag-curaao","flag-christmas-island","flag-cyprus","flag-czechia","flag-germany","flag-diego-garcia","flag-djibouti","flag-denmark","flag-dominica","flag-dominican-republic","flag-algeria","flag-ceuta--melilla","flag-ecuador","flag-estonia","flag-egypt","flag-western-sahara","flag-eritrea","flag-spain","flag-ethiopia","flag-european-union","flag-finland","flag-fiji","flag-falkland-islands","flag-micronesia","flag-faroe-islands","flag-france","flag-gabon","flag-united-kingdom","flag-grenada","flag-georgia","flag-french-guiana","flag-guernsey","flag-ghana","flag-gibraltar","flag-greenland","flag-gambia","flag-guinea","flag-guadeloupe","flag-equatorial-guinea","flag-greece","flag-south-georgia--south-sandwich-islands","flag-guatemala","flag-guam","flag-guineabissau","flag-guyana","flag-hong-kong-sar-china","flag-heard--mcdonald-islands","flag-honduras","flag-croatia","flag-haiti","flag-hungary","flag-canary-islands","flag-indonesia","flag-ireland","flag-israel","flag-isle-of-man","flag-india","flag-british-indian-ocean-territory","flag-iraq","flag-iran","flag-iceland","flag-italy","flag-jersey","flag-jamaica","flag-jordan","flag-japan","flag-kenya","flag-kyrgyzstan","flag-cambodia","flag-kiribati","flag-comoros","flag-st-kitts--nevis","flag-north-korea","flag-south-korea","flag-kuwait","flag-cayman-islands","flag-kazakhstan","flag-laos","flag-lebanon","flag-st-lucia","flag-liechtenstein","flag-sri-lanka","flag-liberia","flag-lesotho","flag-lithuania","flag-luxembourg","flag-latvia","flag-libya","flag-morocco","flag-monaco","flag-moldova","flag-montenegro","flag-st-martin","flag-madagascar","flag-marshall-islands","flag-north-macedonia","flag-mali","flag-myanmar-burma","flag-mongolia","flag-macao-sar-china","flag-northern-mariana-islands","flag-martinique","flag-mauritania","flag-montserrat","flag-malta","flag-mauritius","flag-maldives","flag-malawi","flag-mexico","flag-malaysia","flag-mozambique","flag-namibia","flag-new-caledonia","flag-niger","flag-norfolk-island","flag-nigeria","flag-nicaragua","flag-netherlands","flag-norway","flag-nepal","flag-nauru","flag-niue","flag-new-zealand","flag-oman","flag-panama","flag-peru","flag-french-polynesia","flag-papua-new-guinea","flag-philippines","flag-pakistan","flag-poland","flag-st-pierre--miquelon","flag-pitcairn-islands","flag-puerto-rico","flag-palestinian-territories","flag-portugal","flag-palau","flag-paraguay","flag-qatar","flag-runion","flag-romania","flag-serbia","flag-russia","flag-rwanda","flag-saudi-arabia","flag-solomon-islands","flag-seychelles","flag-sudan","flag-sweden","flag-singapore","flag-st-helena","flag-slovenia","flag-svalbard--jan-mayen","flag-slovakia","flag-sierra-leone","flag-san-marino","flag-senegal","flag-somalia","flag-suriname","flag-south-sudan","flag-so-tom--prncipe","flag-el-salvador","flag-sint-maarten","flag-syria","flag-eswatini","flag-tristan-da-cunha","flag-turks--caicos-islands","flag-chad","flag-french-southern-territories","flag-togo","flag-thailand","flag-tajikistan","flag-tokelau","flag-timorleste","flag-turkmenistan","flag-tunisia","flag-tonga","flag-turkey","flag-trinidad--tobago","flag-tuvalu","flag-taiwan","flag-tanzania","flag-ukraine","flag-uganda","flag-us-outlying-islands","flag-united-nations","flag-united-states","flag-uruguay","flag-uzbekistan","flag-vatican-city","flag-st-vincent--grenadines","flag-venezuela","flag-british-virgin-islands","flag-us-virgin-islands","flag-vietnam","flag-vanuatu","flag-wallis--futuna","flag-samoa","flag-kosovo","flag-yemen","flag-mayotte","flag-south-africa","flag-zambia","flag-zimbabwe","flag-england","flag-scotland","flag-wales"]}],"emojis":{"grinning-face":{"a":"Grinning Face","b":"1F600","j":["face","grin","smile","happy","joy",":D"]},"grinning-face-with-big-eyes":{"a":"Grinning Face with Big Eyes","b":"1F603","j":["face","mouth","open","smile","happy","joy","haha",":D",":)","funny"]},"grinning-face-with-smiling-eyes":{"a":"Grinning Face with Smiling Eyes","b":"1F604","j":["eye","face","mouth","open","smile","happy","joy","funny","haha","laugh","like",":D",":)"]},"beaming-face-with-smiling-eyes":{"a":"Beaming Face with Smiling Eyes","b":"1F601","j":["eye","face","grin","smile","happy","joy","kawaii"]},"grinning-squinting-face":{"a":"Grinning Squinting Face","b":"1F606","j":["face","laugh","mouth","satisfied","smile","happy","joy","lol","haha","glad","XD"]},"grinning-face-with-sweat":{"a":"Grinning Face with Sweat","b":"1F605","j":["cold","face","open","smile","sweat","hot","happy","laugh","relief"]},"rolling-on-the-floor-laughing":{"a":"Rolling on the Floor Laughing","b":"1F923","j":["face","floor","laugh","rofl","rolling","rotfl","laughing","lol","haha"]},"face-with-tears-of-joy":{"a":"Face with Tears of Joy","b":"1F602","j":["face","joy","laugh","tear","cry","tears","weep","happy","happytears","haha"]},"slightly-smiling-face":{"a":"Slightly Smiling Face","b":"1F642","j":["face","smile"]},"upsidedown-face":{"a":"Upside-Down Face","b":"1F643","j":["face","upside-down","upside_down_face","flipped","silly","smile"]},"winking-face":{"a":"Winking Face","b":"1F609","j":["face","wink","happy","mischievous","secret",";)","smile","eye"]},"smiling-face-with-smiling-eyes":{"a":"Smiling Face with Smiling Eyes","b":"1F60A","j":["blush","eye","face","smile","happy","flushed","crush","embarrassed","shy","joy"]},"smiling-face-with-halo":{"a":"Smiling Face with Halo","b":"1F607","j":["angel","face","fantasy","halo","innocent","heaven"]},"smiling-face-with-hearts":{"a":"Smiling Face with Hearts","b":"1F970","j":["adore","crush","hearts","in love","face","love","like","affection","valentines","infatuation"]},"smiling-face-with-hearteyes":{"a":"Smiling Face with Heart-Eyes","b":"1F60D","j":["eye","face","love","smile","smiling face with heart-eyes","smiling_face_with_heart_eyes","like","affection","valentines","infatuation","crush","heart"]},"starstruck":{"a":"Star-Struck","b":"1F929","j":["eyes","face","grinning","star","star-struck","starry-eyed","star_struck","smile","starry"]},"face-blowing-a-kiss":{"a":"Face Blowing a Kiss","b":"1F618","j":["face","kiss","love","like","affection","valentines","infatuation"]},"kissing-face":{"a":"Kissing Face","b":"1F617","j":["face","kiss","love","like","3","valentines","infatuation"]},"smiling-face":{"a":"Smiling Face","b":"263A","j":["face","outlined","relaxed","smile","blush","massage","happiness"]},"kissing-face-with-closed-eyes":{"a":"Kissing Face with Closed Eyes","b":"1F61A","j":["closed","eye","face","kiss","love","like","affection","valentines","infatuation"]},"kissing-face-with-smiling-eyes":{"a":"Kissing Face with Smiling Eyes","b":"1F619","j":["eye","face","kiss","smile","affection","valentines","infatuation"]},"smiling-face-with-tear":{"a":"Smiling Face with Tear","b":"1F972","j":["grateful","proud","relieved","smiling","tear","touched","sad","cry","pretend"]},"face-savoring-food":{"a":"Face Savoring Food","b":"1F60B","j":["delicious","face","savouring","smile","yum","happy","joy","tongue","silly","yummy","nom"]},"face-with-tongue":{"a":"Face with Tongue","b":"1F61B","j":["face","tongue","prank","childish","playful","mischievous","smile"]},"winking-face-with-tongue":{"a":"Winking Face with Tongue","b":"1F61C","j":["eye","face","joke","tongue","wink","prank","childish","playful","mischievous","smile"]},"zany-face":{"a":"Zany Face","b":"1F92A","j":["eye","goofy","large","small","face","crazy"]},"squinting-face-with-tongue":{"a":"Squinting Face with Tongue","b":"1F61D","j":["eye","face","horrible","taste","tongue","prank","playful","mischievous","smile"]},"moneymouth-face":{"a":"Money-Mouth Face","b":"1F911","j":["face","money","money-mouth face","mouth","money_mouth_face","rich","dollar"]},"hugging-face":{"a":"Hugging Face","b":"1F917","j":["face","hug","hugging","smile"]},"face-with-hand-over-mouth":{"a":"Face with Hand over Mouth","b":"1F92D","j":["whoops","shock","sudden realization","surprise","face"]},"shushing-face":{"a":"Shushing Face","b":"1F92B","j":["quiet","shush","face","shhh"]},"thinking-face":{"a":"Thinking Face","b":"1F914","j":["face","thinking","hmmm","think","consider"]},"zippermouth-face":{"a":"Zipper-Mouth Face","b":"1F910","j":["face","mouth","zipper","zipper-mouth face","zipper_mouth_face","sealed","secret"]},"face-with-raised-eyebrow":{"a":"Face with Raised Eyebrow","b":"1F928","j":["distrust","skeptic","disapproval","disbelief","mild surprise","scepticism","face","surprise"]},"neutral-face":{"a":"Neutral Face","b":"1F610","j":["deadpan","face","meh","neutral","indifference",":|"]},"expressionless-face":{"a":"Expressionless Face","b":"1F611","j":["expressionless","face","inexpressive","meh","unexpressive","indifferent","-_-","deadpan"]},"face-without-mouth":{"a":"Face Without Mouth","b":"1F636","j":["face","mouth","quiet","silent","hellokitty"]},"face-in-clouds":{"a":"⊛ Face in Clouds","b":"1F636-200D-1F32B-FE0F","j":["absentminded","face in clouds","face in the fog","head in clouds"]},"smirking-face":{"a":"Smirking Face","b":"1F60F","j":["face","smirk","smile","mean","prank","smug","sarcasm"]},"unamused-face":{"a":"Unamused Face","b":"1F612","j":["face","unamused","unhappy","indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","side_eye"]},"face-with-rolling-eyes":{"a":"Face with Rolling Eyes","b":"1F644","j":["eyeroll","eyes","face","rolling","frustrated"]},"grimacing-face":{"a":"Grimacing Face","b":"1F62C","j":["face","grimace","teeth"]},"face-exhaling":{"a":"⊛ Face Exhaling","b":"1F62E-200D-1F4A8","j":["exhale","face exhaling","gasp","groan","relief","whisper","whistle"]},"lying-face":{"a":"Lying Face","b":"1F925","j":["face","lie","pinocchio"]},"relieved-face":{"a":"Relieved Face","b":"1F60C","j":["face","relieved","relaxed","phew","massage","happiness"]},"pensive-face":{"a":"Pensive Face","b":"1F614","j":["dejected","face","pensive","sad","depressed","upset"]},"sleepy-face":{"a":"Sleepy Face","b":"1F62A","j":["face","sleep","tired","rest","nap"]},"drooling-face":{"a":"Drooling Face","b":"1F924","j":["drooling","face"]},"sleeping-face":{"a":"Sleeping Face","b":"1F634","j":["face","sleep","zzz","tired","sleepy","night"]},"face-with-medical-mask":{"a":"Face with Medical Mask","b":"1F637","j":["cold","doctor","face","mask","sick","ill","disease"]},"face-with-thermometer":{"a":"Face with Thermometer","b":"1F912","j":["face","ill","sick","thermometer","temperature","cold","fever"]},"face-with-headbandage":{"a":"Face with Head-Bandage","b":"1F915","j":["bandage","face","face with head-bandage","hurt","injury","face_with_head_bandage","injured","clumsy"]},"nauseated-face":{"a":"Nauseated Face","b":"1F922","j":["face","nauseated","vomit","gross","green","sick","throw up","ill"]},"face-vomiting":{"a":"Face Vomiting","b":"1F92E","j":["puke","sick","vomit","face"]},"sneezing-face":{"a":"Sneezing Face","b":"1F927","j":["face","gesundheit","sneeze","sick","allergy"]},"hot-face":{"a":"Hot Face","b":"1F975","j":["feverish","heat stroke","hot","red-faced","sweating","face","heat","red"]},"cold-face":{"a":"Cold Face","b":"1F976","j":["blue-faced","cold","freezing","frostbite","icicles","face","blue","frozen"]},"woozy-face":{"a":"Woozy Face","b":"1F974","j":["dizzy","intoxicated","tipsy","uneven eyes","wavy mouth","face","wavy"]},"knockedout-face":{"a":"Knocked-out Face","b":"1F635","j":["dead","face","knocked out","knocked-out face","dizzy_face","spent","unconscious","xox","dizzy"]},"face-with-spiral-eyes":{"a":"⊛ Face with Spiral Eyes","b":"1F635-200D-1F4AB","j":["dizzy","face with spiral eyes","hypnotized","spiral","trouble","whoa"]},"exploding-head":{"a":"Exploding Head","b":"1F92F","j":["mind blown","shocked","face","mind","blown"]},"cowboy-hat-face":{"a":"Cowboy Hat Face","b":"1F920","j":["cowboy","cowgirl","face","hat"]},"partying-face":{"a":"Partying Face","b":"1F973","j":["celebration","hat","horn","party","face","woohoo"]},"disguised-face":{"a":"Disguised Face","b":"1F978","j":["disguise","face","glasses","incognito","nose","pretent","brows","moustache"]},"smiling-face-with-sunglasses":{"a":"Smiling Face with Sunglasses","b":"1F60E","j":["bright","cool","face","sun","sunglasses","smile","summer","beach","sunglass"]},"nerd-face":{"a":"Nerd Face","b":"1F913","j":["face","geek","nerd","nerdy","dork"]},"face-with-monocle":{"a":"Face with Monocle","b":"1F9D0","j":["stuffy","wealthy","face"]},"confused-face":{"a":"Confused Face","b":"1F615","j":["confused","face","meh","indifference","huh","weird","hmmm",":/"]},"worried-face":{"a":"Worried Face","b":"1F61F","j":["face","worried","concern","nervous",":("]},"slightly-frowning-face":{"a":"Slightly Frowning Face","b":"1F641","j":["face","frown","frowning","disappointed","sad","upset"]},"frowning-face":{"a":"Frowning Face","b":"2639","j":["face","frown","sad","upset"]},"face-with-open-mouth":{"a":"Face with Open Mouth","b":"1F62E","j":["face","mouth","open","sympathy","surprise","impressed","wow","whoa",":O"]},"hushed-face":{"a":"Hushed Face","b":"1F62F","j":["face","hushed","stunned","surprised","woo","shh"]},"astonished-face":{"a":"Astonished Face","b":"1F632","j":["astonished","face","shocked","totally","xox","surprised","poisoned"]},"flushed-face":{"a":"Flushed Face","b":"1F633","j":["dazed","face","flushed","blush","shy","flattered"]},"pleading-face":{"a":"Pleading Face","b":"1F97A","j":["begging","mercy","puppy eyes","face"]},"frowning-face-with-open-mouth":{"a":"Frowning Face with Open Mouth","b":"1F626","j":["face","frown","mouth","open","aw","what"]},"anguished-face":{"a":"Anguished Face","b":"1F627","j":["anguished","face","stunned","nervous"]},"fearful-face":{"a":"Fearful Face","b":"1F628","j":["face","fear","fearful","scared","terrified","nervous","oops","huh"]},"anxious-face-with-sweat":{"a":"Anxious Face with Sweat","b":"1F630","j":["blue","cold","face","rushed","sweat","nervous"]},"sad-but-relieved-face":{"a":"Sad but Relieved Face","b":"1F625","j":["disappointed","face","relieved","whew","phew","sweat","nervous"]},"crying-face":{"a":"Crying Face","b":"1F622","j":["cry","face","sad","tear","tears","depressed","upset",":'("]},"loudly-crying-face":{"a":"Loudly Crying Face","b":"1F62D","j":["cry","face","sad","sob","tear","tears","upset","depressed"]},"face-screaming-in-fear":{"a":"Face Screaming in Fear","b":"1F631","j":["face","fear","munch","scared","scream","omg"]},"confounded-face":{"a":"Confounded Face","b":"1F616","j":["confounded","face","confused","sick","unwell","oops",":S"]},"persevering-face":{"a":"Persevering Face","b":"1F623","j":["face","persevere","sick","no","upset","oops"]},"disappointed-face":{"a":"Disappointed Face","b":"1F61E","j":["disappointed","face","sad","upset","depressed",":("]},"downcast-face-with-sweat":{"a":"Downcast Face with Sweat","b":"1F613","j":["cold","face","sweat","hot","sad","tired","exercise"]},"weary-face":{"a":"Weary Face","b":"1F629","j":["face","tired","weary","sleepy","sad","frustrated","upset"]},"tired-face":{"a":"Tired Face","b":"1F62B","j":["face","tired","sick","whine","upset","frustrated"]},"yawning-face":{"a":"Yawning Face","b":"1F971","j":["bored","tired","yawn","sleepy",""]},"face-with-steam-from-nose":{"a":"Face with Steam From Nose","b":"1F624","j":["face","triumph","won","gas","phew","proud","pride"]},"pouting-face":{"a":"Pouting Face","b":"1F621","j":["angry","face","mad","pouting","rage","red","hate","despise"]},"angry-face":{"a":"Angry Face","b":"1F620","j":["anger","angry","face","mad","annoyed","frustrated"]},"face-with-symbols-on-mouth":{"a":"Face with Symbols on Mouth","b":"1F92C","j":["swearing","cursing","face","cussing","profanity","expletive"]},"smiling-face-with-horns":{"a":"Smiling Face with Horns","b":"1F608","j":["face","fairy tale","fantasy","horns","smile","devil"]},"angry-face-with-horns":{"a":"Angry Face with Horns","b":"1F47F","j":["demon","devil","face","fantasy","imp","angry","horns"]},"skull":{"a":"Skull","b":"1F480","j":["death","face","fairy tale","monster","dead","skeleton","creepy"]},"skull-and-crossbones":{"a":"Skull and Crossbones","b":"2620","j":["crossbones","death","face","monster","skull","poison","danger","deadly","scary","pirate","evil"]},"pile-of-poo":{"a":"Pile of Poo","b":"1F4A9","j":["dung","face","monster","poo","poop","hankey","shitface","fail","turd","shit"]},"clown-face":{"a":"Clown Face","b":"1F921","j":["clown","face"]},"ogre":{"a":"Ogre","b":"1F479","j":["creature","face","fairy tale","fantasy","monster","troll","red","mask","halloween","scary","creepy","devil","demon","japanese"]},"goblin":{"a":"Goblin","b":"1F47A","j":["creature","face","fairy tale","fantasy","monster","red","evil","mask","scary","creepy","japanese"]},"ghost":{"a":"Ghost","b":"1F47B","j":["creature","face","fairy tale","fantasy","monster","halloween","spooky","scary"]},"alien":{"a":"Alien","b":"1F47D","j":["creature","extraterrestrial","face","fantasy","ufo","UFO","paul","weird","outer_space"]},"alien-monster":{"a":"Alien Monster","b":"1F47E","j":["alien","creature","extraterrestrial","face","monster","ufo","game","arcade","play"]},"robot":{"a":"Robot","b":"1F916","j":["face","monster","computer","machine","bot"]},"grinning-cat":{"a":"Grinning Cat","b":"1F63A","j":["cat","face","grinning","mouth","open","smile","animal","cats","happy"]},"grinning-cat-with-smiling-eyes":{"a":"Grinning Cat with Smiling Eyes","b":"1F638","j":["cat","eye","face","grin","smile","animal","cats"]},"cat-with-tears-of-joy":{"a":"Cat with Tears of Joy","b":"1F639","j":["cat","face","joy","tear","animal","cats","haha","happy","tears"]},"smiling-cat-with-hearteyes":{"a":"Smiling Cat with Heart-Eyes","b":"1F63B","j":["cat","eye","face","heart","love","smile","smiling cat with heart-eyes","smiling_cat_with_heart_eyes","animal","like","affection","cats","valentines"]},"cat-with-wry-smile":{"a":"Cat with Wry Smile","b":"1F63C","j":["cat","face","ironic","smile","wry","animal","cats","smirk"]},"kissing-cat":{"a":"Kissing Cat","b":"1F63D","j":["cat","eye","face","kiss","animal","cats"]},"weary-cat":{"a":"Weary Cat","b":"1F640","j":["cat","face","oh","surprised","weary","animal","cats","munch","scared","scream"]},"crying-cat":{"a":"Crying Cat","b":"1F63F","j":["cat","cry","face","sad","tear","animal","tears","weep","cats","upset"]},"pouting-cat":{"a":"Pouting Cat","b":"1F63E","j":["cat","face","pouting","animal","cats"]},"seenoevil-monkey":{"a":"See-No-Evil Monkey","b":"1F648","j":["evil","face","forbidden","monkey","see","see-no-evil monkey","see_no_evil_monkey","animal","nature","haha"]},"hearnoevil-monkey":{"a":"Hear-No-Evil Monkey","b":"1F649","j":["evil","face","forbidden","hear","hear-no-evil monkey","monkey","hear_no_evil_monkey","animal","nature"]},"speaknoevil-monkey":{"a":"Speak-No-Evil Monkey","b":"1F64A","j":["evil","face","forbidden","monkey","speak","speak-no-evil monkey","speak_no_evil_monkey","animal","nature","omg"]},"kiss-mark":{"a":"Kiss Mark","b":"1F48B","j":["kiss","lips","face","love","like","affection","valentines"]},"love-letter":{"a":"Love Letter","b":"1F48C","j":["heart","letter","love","mail","email","like","affection","envelope","valentines"]},"heart-with-arrow":{"a":"Heart with Arrow","b":"1F498","j":["arrow","cupid","love","like","heart","affection","valentines"]},"heart-with-ribbon":{"a":"Heart with Ribbon","b":"1F49D","j":["ribbon","valentine","love","valentines"]},"sparkling-heart":{"a":"Sparkling Heart","b":"1F496","j":["excited","sparkle","love","like","affection","valentines"]},"growing-heart":{"a":"Growing Heart","b":"1F497","j":["excited","growing","nervous","pulse","like","love","affection","valentines","pink"]},"beating-heart":{"a":"Beating Heart","b":"1F493","j":["beating","heartbeat","pulsating","love","like","affection","valentines","pink","heart"]},"revolving-hearts":{"a":"Revolving Hearts","b":"1F49E","j":["revolving","love","like","affection","valentines"]},"two-hearts":{"a":"Two Hearts","b":"1F495","j":["love","like","affection","valentines","heart"]},"heart-decoration":{"a":"Heart Decoration","b":"1F49F","j":["heart","purple-square","love","like"]},"heart-exclamation":{"a":"Heart Exclamation","b":"2763","j":["exclamation","mark","punctuation","decoration","love"]},"broken-heart":{"a":"Broken Heart","b":"1F494","j":["break","broken","sad","sorry","heart","heartbreak"]},"heart-on-fire":{"a":"⊛ Heart on Fire","b":"2764-FE0F-200D-1F525","j":["burn","heart","heart on fire","love","lust","sacred heart"]},"mending-heart":{"a":"⊛ Mending Heart","b":"2764-FE0F-200D-1FA79","j":["healthier","improving","mending","mending heart","recovering","recuperating","well"]},"red-heart":{"a":"Red Heart","b":"2764","j":["heart","love","like","valentines"]},"orange-heart":{"a":"Orange Heart","b":"1F9E1","j":["orange","love","like","affection","valentines"]},"yellow-heart":{"a":"Yellow Heart","b":"1F49B","j":["yellow","love","like","affection","valentines"]},"green-heart":{"a":"Green Heart","b":"1F49A","j":["green","love","like","affection","valentines"]},"blue-heart":{"a":"Blue Heart","b":"1F499","j":["blue","love","like","affection","valentines"]},"purple-heart":{"a":"Purple Heart","b":"1F49C","j":["purple","love","like","affection","valentines"]},"brown-heart":{"a":"Brown Heart","b":"1F90E","j":["brown","heart","coffee"]},"black-heart":{"a":"Black Heart","b":"1F5A4","j":["black","evil","wicked"]},"white-heart":{"a":"White Heart","b":"1F90D","j":["heart","white","pure"]},"hundred-points":{"a":"Hundred Points","b":"1F4AF","j":["100","full","hundred","score","perfect","numbers","century","exam","quiz","test","pass"]},"anger-symbol":{"a":"Anger Symbol","b":"1F4A2","j":["angry","comic","mad"]},"collision":{"a":"Collision","b":"1F4A5","j":["boom","comic","bomb","explode","explosion","blown"]},"dizzy":{"a":"Dizzy","b":"1F4AB","j":["comic","star","sparkle","shoot","magic"]},"sweat-droplets":{"a":"Sweat Droplets","b":"1F4A6","j":["comic","splashing","sweat","water","drip","oops"]},"dashing-away":{"a":"Dashing Away","b":"1F4A8","j":["comic","dash","running","wind","air","fast","shoo","fart","smoke","puff"]},"hole":{"a":"Hole","b":"1F573","j":["embarrassing"]},"bomb":{"a":"Bomb","b":"1F4A3","j":["comic","boom","explode","explosion","terrorism"]},"speech-balloon":{"a":"Speech Balloon","b":"1F4AC","j":["balloon","bubble","comic","dialog","speech","words","message","talk","chatting"]},"eye-in-speech-bubble":{"a":"Eye in Speech Bubble","b":"1F441-FE0F-200D-1F5E8-FE0F","j":["eye","speech bubble","witness","info"]},"left-speech-bubble":{"a":"Left Speech Bubble","b":"1F5E8","j":["dialog","speech","words","message","talk","chatting"]},"right-anger-bubble":{"a":"Right Anger Bubble","b":"1F5EF","j":["angry","balloon","bubble","mad","caption","speech","thinking"]},"thought-balloon":{"a":"Thought Balloon","b":"1F4AD","j":["balloon","bubble","comic","thought","cloud","speech","thinking","dream"]},"zzz":{"a":"Zzz","b":"1F4A4","j":["comic","sleep","sleepy","tired","dream"]},"waving-hand":{"a":"Waving Hand","b":"1F44B","j":["hand","wave","waving","hands","gesture","goodbye","solong","farewell","hello","hi","palm"]},"raised-back-of-hand":{"a":"Raised Back of Hand","b":"1F91A","j":["backhand","raised","fingers"]},"hand-with-fingers-splayed":{"a":"Hand with Fingers Splayed","b":"1F590","j":["finger","hand","splayed","fingers","palm"]},"raised-hand":{"a":"Raised Hand","b":"270B","j":["hand","high 5","high five","fingers","stop","highfive","palm","ban"]},"vulcan-salute":{"a":"Vulcan Salute","b":"1F596","j":["finger","hand","spock","vulcan","fingers","star trek"]},"ok-hand":{"a":"Ok Hand","b":"1F44C","j":["hand","OK","fingers","limbs","perfect","ok","okay"]},"pinched-fingers":{"a":"Pinched Fingers","b":"1F90C","j":["fingers","hand gesture","interrogation","pinched","sarcastic","size","tiny","small"]},"pinching-hand":{"a":"Pinching Hand","b":"1F90F","j":["small amount","tiny","small","size"]},"victory-hand":{"a":"Victory Hand","b":"270C","j":["hand","v","victory","fingers","ohyeah","peace","two"]},"crossed-fingers":{"a":"Crossed Fingers","b":"1F91E","j":["cross","finger","hand","luck","good","lucky"]},"loveyou-gesture":{"a":"Love-You Gesture","b":"1F91F","j":["hand","ILY","love-you gesture","love_you_gesture","fingers","gesture"]},"sign-of-the-horns":{"a":"Sign of the Horns","b":"1F918","j":["finger","hand","horns","rock-on","fingers","evil_eye","sign_of_horns","rock_on"]},"call-me-hand":{"a":"Call Me Hand","b":"1F919","j":["call","hand","hands","gesture"]},"backhand-index-pointing-left":{"a":"Backhand Index Pointing Left","b":"1F448","j":["backhand","finger","hand","index","point","direction","fingers","left"]},"backhand-index-pointing-right":{"a":"Backhand Index Pointing Right","b":"1F449","j":["backhand","finger","hand","index","point","fingers","direction","right"]},"backhand-index-pointing-up":{"a":"Backhand Index Pointing Up","b":"1F446","j":["backhand","finger","hand","point","up","fingers","direction"]},"middle-finger":{"a":"Middle Finger","b":"1F595","j":["finger","hand","fingers","rude","middle","flipping"]},"backhand-index-pointing-down":{"a":"Backhand Index Pointing Down","b":"1F447","j":["backhand","down","finger","hand","point","fingers","direction"]},"index-pointing-up":{"a":"Index Pointing Up","b":"261D","j":["finger","hand","index","point","up","fingers","direction"]},"thumbs-up":{"a":"Thumbs Up","b":"1F44D","j":["+1","hand","thumb","up","thumbsup","yes","awesome","good","agree","accept","cool","like"]},"thumbs-down":{"a":"Thumbs Down","b":"1F44E","j":["-1","down","hand","thumb","thumbsdown","no","dislike"]},"raised-fist":{"a":"Raised Fist","b":"270A","j":["clenched","fist","hand","punch","fingers","grasp"]},"oncoming-fist":{"a":"Oncoming Fist","b":"1F44A","j":["clenched","fist","hand","punch","angry","violence","hit","attack"]},"leftfacing-fist":{"a":"Left-Facing Fist","b":"1F91B","j":["fist","left-facing fist","leftwards","left_facing_fist","hand","fistbump"]},"rightfacing-fist":{"a":"Right-Facing Fist","b":"1F91C","j":["fist","right-facing fist","rightwards","right_facing_fist","hand","fistbump"]},"clapping-hands":{"a":"Clapping Hands","b":"1F44F","j":["clap","hand","hands","praise","applause","congrats","yay"]},"raising-hands":{"a":"Raising Hands","b":"1F64C","j":["celebration","gesture","hand","hooray","raised","yea","hands"]},"open-hands":{"a":"Open Hands","b":"1F450","j":["hand","open","fingers","butterfly","hands"]},"palms-up-together":{"a":"Palms Up Together","b":"1F932","j":["prayer","cupped hands","hands","gesture","cupped"]},"handshake":{"a":"Handshake","b":"1F91D","j":["agreement","hand","meeting","shake"]},"folded-hands":{"a":"Folded Hands","b":"1F64F","j":["ask","hand","high 5","high five","please","pray","thanks","hope","wish","namaste","highfive"]},"writing-hand":{"a":"Writing Hand","b":"270D","j":["hand","write","lower_left_ballpoint_pen","stationery","compose"]},"nail-polish":{"a":"Nail Polish","b":"1F485","j":["care","cosmetics","manicure","nail","polish","beauty","finger","fashion"]},"selfie":{"a":"Selfie","b":"1F933","j":["camera","phone"]},"flexed-biceps":{"a":"Flexed Biceps","b":"1F4AA","j":["biceps","comic","flex","muscle","arm","hand","summer","strong"]},"mechanical-arm":{"a":"Mechanical Arm","b":"1F9BE","j":["accessibility","prosthetic"]},"mechanical-leg":{"a":"Mechanical Leg","b":"1F9BF","j":["accessibility","prosthetic"]},"leg":{"a":"Leg","b":"1F9B5","j":["kick","limb"]},"foot":{"a":"Foot","b":"1F9B6","j":["kick","stomp"]},"ear":{"a":"Ear","b":"1F442","j":["body","face","hear","sound","listen"]},"ear-with-hearing-aid":{"a":"Ear with Hearing Aid","b":"1F9BB","j":["accessibility","hard of hearing"]},"nose":{"a":"Nose","b":"1F443","j":["body","smell","sniff"]},"brain":{"a":"Brain","b":"1F9E0","j":["intelligent","smart"]},"anatomical-heart":{"a":"Anatomical Heart","b":"1FAC0","j":["anatomical","cardiology","heart","organ","pulse","health","heartbeat"]},"lungs":{"a":"Lungs","b":"1FAC1","j":["breath","exhalation","inhalation","organ","respiration","breathe"]},"tooth":{"a":"Tooth","b":"1F9B7","j":["dentist","teeth"]},"bone":{"a":"Bone","b":"1F9B4","j":["skeleton"]},"eyes":{"a":"Eyes","b":"1F440","j":["eye","face","look","watch","stalk","peek","see"]},"eye":{"a":"Eye","b":"1F441","j":["body","face","look","see","watch","stare"]},"tongue":{"a":"Tongue","b":"1F445","j":["body","mouth","playful"]},"mouth":{"a":"Mouth","b":"1F444","j":["lips","kiss"]},"baby":{"a":"Baby","b":"1F476","j":["young","child","boy","girl","toddler"]},"child":{"a":"Child","b":"1F9D2","j":["gender-neutral","unspecified gender","young"]},"boy":{"a":"Boy","b":"1F466","j":["young","man","male","guy","teenager"]},"girl":{"a":"Girl","b":"1F467","j":["Virgo","young","zodiac","female","woman","teenager"]},"person":{"a":"Person","b":"1F9D1","j":["adult","gender-neutral","unspecified gender"]},"person-blond-hair":{"a":"Person: Blond Hair","b":"1F471","j":["blond","blond-haired person","hair","person: blond hair","hairstyle"]},"man":{"a":"Man","b":"1F468","j":["adult","mustache","father","dad","guy","classy","sir","moustache"]},"person-beard":{"a":"Person: Beard","b":"1F9D4","j":["beard","person","person: beard","bewhiskered","man_beard"]},"man-beard":{"a":"⊛ Man: Beard","b":"1F9D4-200D-2642-FE0F","j":["beard","man","man: beard"]},"woman-beard":{"a":"⊛ Woman: Beard","b":"1F9D4-200D-2640-FE0F","j":["beard","woman","woman: beard"]},"man-red-hair":{"a":"Man: Red Hair","b":"1F468-200D-1F9B0","j":["adult","man","red hair","hairstyle"]},"man-curly-hair":{"a":"Man: Curly Hair","b":"1F468-200D-1F9B1","j":["adult","curly hair","man","hairstyle"]},"man-white-hair":{"a":"Man: White Hair","b":"1F468-200D-1F9B3","j":["adult","man","white hair","old","elder"]},"man-bald":{"a":"Man: Bald","b":"1F468-200D-1F9B2","j":["adult","bald","man","hairless"]},"woman":{"a":"Woman","b":"1F469","j":["adult","female","girls","lady"]},"woman-red-hair":{"a":"Woman: Red Hair","b":"1F469-200D-1F9B0","j":["adult","red hair","woman","hairstyle"]},"person-red-hair":{"a":"Person: Red Hair","b":"1F9D1-200D-1F9B0","j":["adult","gender-neutral","person","red hair","unspecified gender","hairstyle"]},"woman-curly-hair":{"a":"Woman: Curly Hair","b":"1F469-200D-1F9B1","j":["adult","curly hair","woman","hairstyle"]},"person-curly-hair":{"a":"Person: Curly Hair","b":"1F9D1-200D-1F9B1","j":["adult","curly hair","gender-neutral","person","unspecified gender","hairstyle"]},"woman-white-hair":{"a":"Woman: White Hair","b":"1F469-200D-1F9B3","j":["adult","white hair","woman","old","elder"]},"person-white-hair":{"a":"Person: White Hair","b":"1F9D1-200D-1F9B3","j":["adult","gender-neutral","person","unspecified gender","white hair","elder","old"]},"woman-bald":{"a":"Woman: Bald","b":"1F469-200D-1F9B2","j":["adult","bald","woman","hairless"]},"person-bald":{"a":"Person: Bald","b":"1F9D1-200D-1F9B2","j":["adult","bald","gender-neutral","person","unspecified gender","hairless"]},"woman-blond-hair":{"a":"Woman: Blond Hair","b":"1F471-200D-2640-FE0F","j":["blond-haired woman","blonde","hair","woman","woman: blond hair","female","girl","person"]},"man-blond-hair":{"a":"Man: Blond Hair","b":"1F471-200D-2642-FE0F","j":["blond","blond-haired man","hair","man","man: blond hair","male","boy","blonde","guy","person"]},"older-person":{"a":"Older Person","b":"1F9D3","j":["adult","gender-neutral","old","unspecified gender","human","elder","senior"]},"old-man":{"a":"Old Man","b":"1F474","j":["adult","man","old","human","male","men","elder","senior"]},"old-woman":{"a":"Old Woman","b":"1F475","j":["adult","old","woman","human","female","women","lady","elder","senior"]},"person-frowning":{"a":"Person Frowning","b":"1F64D","j":["frown","gesture","worried"]},"man-frowning":{"a":"Man Frowning","b":"1F64D-200D-2642-FE0F","j":["frowning","gesture","man","male","boy","sad","depressed","discouraged","unhappy"]},"woman-frowning":{"a":"Woman Frowning","b":"1F64D-200D-2640-FE0F","j":["frowning","gesture","woman","female","girl","sad","depressed","discouraged","unhappy"]},"person-pouting":{"a":"Person Pouting","b":"1F64E","j":["gesture","pouting","upset"]},"man-pouting":{"a":"Man Pouting","b":"1F64E-200D-2642-FE0F","j":["gesture","man","pouting","male","boy"]},"woman-pouting":{"a":"Woman Pouting","b":"1F64E-200D-2640-FE0F","j":["gesture","pouting","woman","female","girl"]},"person-gesturing-no":{"a":"Person Gesturing No","b":"1F645","j":["forbidden","gesture","hand","person gesturing NO","prohibited","decline"]},"man-gesturing-no":{"a":"Man Gesturing No","b":"1F645-200D-2642-FE0F","j":["forbidden","gesture","hand","man","man gesturing NO","prohibited","male","boy","nope"]},"woman-gesturing-no":{"a":"Woman Gesturing No","b":"1F645-200D-2640-FE0F","j":["forbidden","gesture","hand","prohibited","woman","woman gesturing NO","female","girl","nope"]},"person-gesturing-ok":{"a":"Person Gesturing Ok","b":"1F646","j":["gesture","hand","OK","person gesturing OK","agree"]},"man-gesturing-ok":{"a":"Man Gesturing Ok","b":"1F646-200D-2642-FE0F","j":["gesture","hand","man","man gesturing OK","OK","men","boy","male","blue","human"]},"woman-gesturing-ok":{"a":"Woman Gesturing Ok","b":"1F646-200D-2640-FE0F","j":["gesture","hand","OK","woman","woman gesturing OK","women","girl","female","pink","human"]},"person-tipping-hand":{"a":"Person Tipping Hand","b":"1F481","j":["hand","help","information","sassy","tipping"]},"man-tipping-hand":{"a":"Man Tipping Hand","b":"1F481-200D-2642-FE0F","j":["man","sassy","tipping hand","male","boy","human","information"]},"woman-tipping-hand":{"a":"Woman Tipping Hand","b":"1F481-200D-2640-FE0F","j":["sassy","tipping hand","woman","female","girl","human","information"]},"person-raising-hand":{"a":"Person Raising Hand","b":"1F64B","j":["gesture","hand","happy","raised","question"]},"man-raising-hand":{"a":"Man Raising Hand","b":"1F64B-200D-2642-FE0F","j":["gesture","man","raising hand","male","boy"]},"woman-raising-hand":{"a":"Woman Raising Hand","b":"1F64B-200D-2640-FE0F","j":["gesture","raising hand","woman","female","girl"]},"deaf-person":{"a":"Deaf Person","b":"1F9CF","j":["accessibility","deaf","ear","hear"]},"deaf-man":{"a":"Deaf Man","b":"1F9CF-200D-2642-FE0F","j":["deaf","man","accessibility"]},"deaf-woman":{"a":"Deaf Woman","b":"1F9CF-200D-2640-FE0F","j":["deaf","woman","accessibility"]},"person-bowing":{"a":"Person Bowing","b":"1F647","j":["apology","bow","gesture","sorry","respectiful"]},"man-bowing":{"a":"Man Bowing","b":"1F647-200D-2642-FE0F","j":["apology","bowing","favor","gesture","man","sorry","male","boy"]},"woman-bowing":{"a":"Woman Bowing","b":"1F647-200D-2640-FE0F","j":["apology","bowing","favor","gesture","sorry","woman","female","girl"]},"person-facepalming":{"a":"Person Facepalming","b":"1F926","j":["disbelief","exasperation","face","palm","disappointed"]},"man-facepalming":{"a":"Man Facepalming","b":"1F926-200D-2642-FE0F","j":["disbelief","exasperation","facepalm","man","male","boy"]},"woman-facepalming":{"a":"Woman Facepalming","b":"1F926-200D-2640-FE0F","j":["disbelief","exasperation","facepalm","woman","female","girl"]},"person-shrugging":{"a":"Person Shrugging","b":"1F937","j":["doubt","ignorance","indifference","shrug","regardless"]},"man-shrugging":{"a":"Man Shrugging","b":"1F937-200D-2642-FE0F","j":["doubt","ignorance","indifference","man","shrug","male","boy","confused","indifferent"]},"woman-shrugging":{"a":"Woman Shrugging","b":"1F937-200D-2640-FE0F","j":["doubt","ignorance","indifference","shrug","woman","female","girl","confused","indifferent"]},"health-worker":{"a":"Health Worker","b":"1F9D1-200D-2695-FE0F","j":["doctor","healthcare","nurse","therapist","hospital"]},"man-health-worker":{"a":"Man Health Worker","b":"1F468-200D-2695-FE0F","j":["doctor","healthcare","man","nurse","therapist","human"]},"woman-health-worker":{"a":"Woman Health Worker","b":"1F469-200D-2695-FE0F","j":["doctor","healthcare","nurse","therapist","woman","human"]},"student":{"a":"Student","b":"1F9D1-200D-1F393","j":["graduate","learn"]},"man-student":{"a":"Man Student","b":"1F468-200D-1F393","j":["graduate","man","student","human"]},"woman-student":{"a":"Woman Student","b":"1F469-200D-1F393","j":["graduate","student","woman","human"]},"teacher":{"a":"Teacher","b":"1F9D1-200D-1F3EB","j":["instructor","professor"]},"man-teacher":{"a":"Man Teacher","b":"1F468-200D-1F3EB","j":["instructor","man","professor","teacher","human"]},"woman-teacher":{"a":"Woman Teacher","b":"1F469-200D-1F3EB","j":["instructor","professor","teacher","woman","human"]},"judge":{"a":"Judge","b":"1F9D1-200D-2696-FE0F","j":["justice","scales","law"]},"man-judge":{"a":"Man Judge","b":"1F468-200D-2696-FE0F","j":["judge","justice","man","scales","court","human"]},"woman-judge":{"a":"Woman Judge","b":"1F469-200D-2696-FE0F","j":["judge","justice","scales","woman","court","human"]},"farmer":{"a":"Farmer","b":"1F9D1-200D-1F33E","j":["gardener","rancher","crops"]},"man-farmer":{"a":"Man Farmer","b":"1F468-200D-1F33E","j":["farmer","gardener","man","rancher","human"]},"woman-farmer":{"a":"Woman Farmer","b":"1F469-200D-1F33E","j":["farmer","gardener","rancher","woman","human"]},"cook":{"a":"Cook","b":"1F9D1-200D-1F373","j":["chef","food","kitchen","culinary"]},"man-cook":{"a":"Man Cook","b":"1F468-200D-1F373","j":["chef","cook","man","human"]},"woman-cook":{"a":"Woman Cook","b":"1F469-200D-1F373","j":["chef","cook","woman","human"]},"mechanic":{"a":"Mechanic","b":"1F9D1-200D-1F527","j":["electrician","plumber","tradesperson","worker","technician"]},"man-mechanic":{"a":"Man Mechanic","b":"1F468-200D-1F527","j":["electrician","man","mechanic","plumber","tradesperson","human","wrench"]},"woman-mechanic":{"a":"Woman Mechanic","b":"1F469-200D-1F527","j":["electrician","mechanic","plumber","tradesperson","woman","human","wrench"]},"factory-worker":{"a":"Factory Worker","b":"1F9D1-200D-1F3ED","j":["assembly","factory","industrial","worker","labor"]},"man-factory-worker":{"a":"Man Factory Worker","b":"1F468-200D-1F3ED","j":["assembly","factory","industrial","man","worker","human"]},"woman-factory-worker":{"a":"Woman Factory Worker","b":"1F469-200D-1F3ED","j":["assembly","factory","industrial","woman","worker","human"]},"office-worker":{"a":"Office Worker","b":"1F9D1-200D-1F4BC","j":["architect","business","manager","white-collar"]},"man-office-worker":{"a":"Man Office Worker","b":"1F468-200D-1F4BC","j":["architect","business","man","manager","white-collar","human"]},"woman-office-worker":{"a":"Woman Office Worker","b":"1F469-200D-1F4BC","j":["architect","business","manager","white-collar","woman","human"]},"scientist":{"a":"Scientist","b":"1F9D1-200D-1F52C","j":["biologist","chemist","engineer","physicist","chemistry"]},"man-scientist":{"a":"Man Scientist","b":"1F468-200D-1F52C","j":["biologist","chemist","engineer","man","physicist","scientist","human"]},"woman-scientist":{"a":"Woman Scientist","b":"1F469-200D-1F52C","j":["biologist","chemist","engineer","physicist","scientist","woman","human"]},"technologist":{"a":"Technologist","b":"1F9D1-200D-1F4BB","j":["coder","developer","inventor","software","computer"]},"man-technologist":{"a":"Man Technologist","b":"1F468-200D-1F4BB","j":["coder","developer","inventor","man","software","technologist","engineer","programmer","human","laptop","computer"]},"woman-technologist":{"a":"Woman Technologist","b":"1F469-200D-1F4BB","j":["coder","developer","inventor","software","technologist","woman","engineer","programmer","human","laptop","computer"]},"singer":{"a":"Singer","b":"1F9D1-200D-1F3A4","j":["actor","entertainer","rock","star","song","artist","performer"]},"man-singer":{"a":"Man Singer","b":"1F468-200D-1F3A4","j":["actor","entertainer","man","rock","singer","star","rockstar","human"]},"woman-singer":{"a":"Woman Singer","b":"1F469-200D-1F3A4","j":["actor","entertainer","rock","singer","star","woman","rockstar","human"]},"artist":{"a":"Artist","b":"1F9D1-200D-1F3A8","j":["palette","painting","draw","creativity"]},"man-artist":{"a":"Man Artist","b":"1F468-200D-1F3A8","j":["artist","man","palette","painter","human"]},"woman-artist":{"a":"Woman Artist","b":"1F469-200D-1F3A8","j":["artist","palette","woman","painter","human"]},"pilot":{"a":"Pilot","b":"1F9D1-200D-2708-FE0F","j":["plane","fly","airplane"]},"man-pilot":{"a":"Man Pilot","b":"1F468-200D-2708-FE0F","j":["man","pilot","plane","aviator","human"]},"woman-pilot":{"a":"Woman Pilot","b":"1F469-200D-2708-FE0F","j":["pilot","plane","woman","aviator","human"]},"astronaut":{"a":"Astronaut","b":"1F9D1-200D-1F680","j":["rocket","outerspace"]},"man-astronaut":{"a":"Man Astronaut","b":"1F468-200D-1F680","j":["astronaut","man","rocket","space","human"]},"woman-astronaut":{"a":"Woman Astronaut","b":"1F469-200D-1F680","j":["astronaut","rocket","woman","space","human"]},"firefighter":{"a":"Firefighter","b":"1F9D1-200D-1F692","j":["firetruck","fire"]},"man-firefighter":{"a":"Man Firefighter","b":"1F468-200D-1F692","j":["firefighter","firetruck","man","fireman","human"]},"woman-firefighter":{"a":"Woman Firefighter","b":"1F469-200D-1F692","j":["firefighter","firetruck","woman","fireman","human"]},"police-officer":{"a":"Police Officer","b":"1F46E","j":["cop","officer","police"]},"man-police-officer":{"a":"Man Police Officer","b":"1F46E-200D-2642-FE0F","j":["cop","man","officer","police","law","legal","enforcement","arrest","911"]},"woman-police-officer":{"a":"Woman Police Officer","b":"1F46E-200D-2640-FE0F","j":["cop","officer","police","woman","law","legal","enforcement","arrest","911","female"]},"detective":{"a":"Detective","b":"1F575","j":["sleuth","spy","human"]},"man-detective":{"a":"Man Detective","b":"1F575-FE0F-200D-2642-FE0F","j":["detective","man","sleuth","spy","crime"]},"woman-detective":{"a":"Woman Detective","b":"1F575-FE0F-200D-2640-FE0F","j":["detective","sleuth","spy","woman","human","female"]},"guard":{"a":"Guard","b":"1F482","j":["protect"]},"man-guard":{"a":"Man Guard","b":"1F482-200D-2642-FE0F","j":["guard","man","uk","gb","british","male","guy","royal"]},"woman-guard":{"a":"Woman Guard","b":"1F482-200D-2640-FE0F","j":["guard","woman","uk","gb","british","female","royal"]},"ninja":{"a":"Ninja","b":"1F977","j":["fighter","hidden","stealth","ninjutsu","skills","japanese"]},"construction-worker":{"a":"Construction Worker","b":"1F477","j":["construction","hat","worker","labor","build"]},"man-construction-worker":{"a":"Man Construction Worker","b":"1F477-200D-2642-FE0F","j":["construction","man","worker","male","human","wip","guy","build","labor"]},"woman-construction-worker":{"a":"Woman Construction Worker","b":"1F477-200D-2640-FE0F","j":["construction","woman","worker","female","human","wip","build","labor"]},"prince":{"a":"Prince","b":"1F934","j":["boy","man","male","crown","royal","king"]},"princess":{"a":"Princess","b":"1F478","j":["fairy tale","fantasy","girl","woman","female","blond","crown","royal","queen"]},"person-wearing-turban":{"a":"Person Wearing Turban","b":"1F473","j":["turban","headdress"]},"man-wearing-turban":{"a":"Man Wearing Turban","b":"1F473-200D-2642-FE0F","j":["man","turban","male","indian","hinduism","arabs"]},"woman-wearing-turban":{"a":"Woman Wearing Turban","b":"1F473-200D-2640-FE0F","j":["turban","woman","female","indian","hinduism","arabs"]},"person-with-skullcap":{"a":"Person with Skullcap","b":"1F472","j":["cap","gua pi mao","hat","person","skullcap","man_with_skullcap","male","boy","chinese"]},"woman-with-headscarf":{"a":"Woman with Headscarf","b":"1F9D5","j":["headscarf","hijab","mantilla","tichel","bandana","head kerchief","female"]},"person-in-tuxedo":{"a":"Person in Tuxedo","b":"1F935","j":["groom","person","tuxedo","man_in_tuxedo","couple","marriage","wedding"]},"man-in-tuxedo":{"a":"Man in Tuxedo","b":"1F935-200D-2642-FE0F","j":["man","tuxedo","formal","fashion"]},"woman-in-tuxedo":{"a":"Woman in Tuxedo","b":"1F935-200D-2640-FE0F","j":["tuxedo","woman","formal","fashion"]},"person-with-veil":{"a":"Person with Veil","b":"1F470","j":["bride","person","veil","wedding","bride_with_veil","couple","marriage","woman"]},"man-with-veil":{"a":"Man with Veil","b":"1F470-200D-2642-FE0F","j":["man","veil","wedding","marriage"]},"woman-with-veil":{"a":"Woman with Veil","b":"1F470-200D-2640-FE0F","j":["veil","woman","wedding","marriage"]},"pregnant-woman":{"a":"Pregnant Woman","b":"1F930","j":["pregnant","woman","baby"]},"breastfeeding":{"a":"Breast-Feeding","b":"1F931","j":["baby","breast","breast-feeding","nursing","breast_feeding"]},"woman-feeding-baby":{"a":"Woman Feeding Baby","b":"1F469-200D-1F37C","j":["baby","feeding","nursing","woman","birth","food"]},"man-feeding-baby":{"a":"Man Feeding Baby","b":"1F468-200D-1F37C","j":["baby","feeding","man","nursing","birth","food"]},"person-feeding-baby":{"a":"Person Feeding Baby","b":"1F9D1-200D-1F37C","j":["baby","feeding","nursing","person","birth","food"]},"baby-angel":{"a":"Baby Angel","b":"1F47C","j":["angel","baby","face","fairy tale","fantasy","heaven","wings","halo"]},"santa-claus":{"a":"Santa Claus","b":"1F385","j":["celebration","Christmas","claus","father","santa","festival","man","male","xmas","father christmas"]},"mrs-claus":{"a":"Mrs. Claus","b":"1F936","j":["celebration","Christmas","claus","mother","Mrs.","woman","female","xmas","mother christmas"]},"mx-claus":{"a":"Mx Claus","b":"1F9D1-200D-1F384","j":["Claus, christmas","christmas"]},"superhero":{"a":"Superhero","b":"1F9B8","j":["good","hero","heroine","superpower","marvel"]},"man-superhero":{"a":"Man Superhero","b":"1F9B8-200D-2642-FE0F","j":["good","hero","man","superpower","male","superpowers"]},"woman-superhero":{"a":"Woman Superhero","b":"1F9B8-200D-2640-FE0F","j":["good","hero","heroine","superpower","woman","female","superpowers"]},"supervillain":{"a":"Supervillain","b":"1F9B9","j":["criminal","evil","superpower","villain","marvel"]},"man-supervillain":{"a":"Man Supervillain","b":"1F9B9-200D-2642-FE0F","j":["criminal","evil","man","superpower","villain","male","bad","hero","superpowers"]},"woman-supervillain":{"a":"Woman Supervillain","b":"1F9B9-200D-2640-FE0F","j":["criminal","evil","superpower","villain","woman","female","bad","heroine","superpowers"]},"mage":{"a":"Mage","b":"1F9D9","j":["sorcerer","sorceress","witch","wizard","magic"]},"man-mage":{"a":"Man Mage","b":"1F9D9-200D-2642-FE0F","j":["sorcerer","wizard","man","male","mage"]},"woman-mage":{"a":"Woman Mage","b":"1F9D9-200D-2640-FE0F","j":["sorceress","witch","woman","female","mage"]},"fairy":{"a":"Fairy","b":"1F9DA","j":["Oberon","Puck","Titania","wings","magical"]},"man-fairy":{"a":"Man Fairy","b":"1F9DA-200D-2642-FE0F","j":["Oberon","Puck","man","male"]},"woman-fairy":{"a":"Woman Fairy","b":"1F9DA-200D-2640-FE0F","j":["Titania","woman","female"]},"vampire":{"a":"Vampire","b":"1F9DB","j":["Dracula","undead","blood","twilight"]},"man-vampire":{"a":"Man Vampire","b":"1F9DB-200D-2642-FE0F","j":["Dracula","undead","man","male","dracula"]},"woman-vampire":{"a":"Woman Vampire","b":"1F9DB-200D-2640-FE0F","j":["undead","woman","female"]},"merperson":{"a":"Merperson","b":"1F9DC","j":["mermaid","merman","merwoman","sea"]},"merman":{"a":"Merman","b":"1F9DC-200D-2642-FE0F","j":["Triton","man","male","triton"]},"mermaid":{"a":"Mermaid","b":"1F9DC-200D-2640-FE0F","j":["merwoman","woman","female","ariel"]},"elf":{"a":"Elf","b":"1F9DD","j":["magical","LOTR style"]},"man-elf":{"a":"Man Elf","b":"1F9DD-200D-2642-FE0F","j":["magical","man","male"]},"woman-elf":{"a":"Woman Elf","b":"1F9DD-200D-2640-FE0F","j":["magical","woman","female"]},"genie":{"a":"Genie","b":"1F9DE","j":["djinn","(non-human color)","magical","wishes"]},"man-genie":{"a":"Man Genie","b":"1F9DE-200D-2642-FE0F","j":["djinn","man","male"]},"woman-genie":{"a":"Woman Genie","b":"1F9DE-200D-2640-FE0F","j":["djinn","woman","female"]},"zombie":{"a":"Zombie","b":"1F9DF","j":["undead","walking dead","(non-human color)","dead"]},"man-zombie":{"a":"Man Zombie","b":"1F9DF-200D-2642-FE0F","j":["undead","walking dead","man","male","dracula"]},"woman-zombie":{"a":"Woman Zombie","b":"1F9DF-200D-2640-FE0F","j":["undead","walking dead","woman","female"]},"person-getting-massage":{"a":"Person Getting Massage","b":"1F486","j":["face","massage","salon","relax"]},"man-getting-massage":{"a":"Man Getting Massage","b":"1F486-200D-2642-FE0F","j":["face","man","massage","male","boy","head"]},"woman-getting-massage":{"a":"Woman Getting Massage","b":"1F486-200D-2640-FE0F","j":["face","massage","woman","female","girl","head"]},"person-getting-haircut":{"a":"Person Getting Haircut","b":"1F487","j":["barber","beauty","haircut","parlor","hairstyle"]},"man-getting-haircut":{"a":"Man Getting Haircut","b":"1F487-200D-2642-FE0F","j":["haircut","man","male","boy"]},"woman-getting-haircut":{"a":"Woman Getting Haircut","b":"1F487-200D-2640-FE0F","j":["haircut","woman","female","girl"]},"person-walking":{"a":"Person Walking","b":"1F6B6","j":["hike","walk","walking","move"]},"man-walking":{"a":"Man Walking","b":"1F6B6-200D-2642-FE0F","j":["hike","man","walk","human","feet","steps"]},"woman-walking":{"a":"Woman Walking","b":"1F6B6-200D-2640-FE0F","j":["hike","walk","woman","human","feet","steps","female"]},"person-standing":{"a":"Person Standing","b":"1F9CD","j":["stand","standing","still"]},"man-standing":{"a":"Man Standing","b":"1F9CD-200D-2642-FE0F","j":["man","standing","still"]},"woman-standing":{"a":"Woman Standing","b":"1F9CD-200D-2640-FE0F","j":["standing","woman","still"]},"person-kneeling":{"a":"Person Kneeling","b":"1F9CE","j":["kneel","kneeling","pray","respectful"]},"man-kneeling":{"a":"Man Kneeling","b":"1F9CE-200D-2642-FE0F","j":["kneeling","man","pray","respectful"]},"woman-kneeling":{"a":"Woman Kneeling","b":"1F9CE-200D-2640-FE0F","j":["kneeling","woman","respectful","pray"]},"person-with-white-cane":{"a":"Person with White Cane","b":"1F9D1-200D-1F9AF","j":["accessibility","blind","person_with_probing_cane"]},"man-with-white-cane":{"a":"Man with White Cane","b":"1F468-200D-1F9AF","j":["accessibility","blind","man","man_with_probing_cane"]},"woman-with-white-cane":{"a":"Woman with White Cane","b":"1F469-200D-1F9AF","j":["accessibility","blind","woman","woman_with_probing_cane"]},"person-in-motorized-wheelchair":{"a":"Person in Motorized Wheelchair","b":"1F9D1-200D-1F9BC","j":["accessibility","wheelchair","disability"]},"man-in-motorized-wheelchair":{"a":"Man in Motorized Wheelchair","b":"1F468-200D-1F9BC","j":["accessibility","man","wheelchair","disability"]},"woman-in-motorized-wheelchair":{"a":"Woman in Motorized Wheelchair","b":"1F469-200D-1F9BC","j":["accessibility","wheelchair","woman","disability"]},"person-in-manual-wheelchair":{"a":"Person in Manual Wheelchair","b":"1F9D1-200D-1F9BD","j":["accessibility","wheelchair","disability"]},"man-in-manual-wheelchair":{"a":"Man in Manual Wheelchair","b":"1F468-200D-1F9BD","j":["accessibility","man","wheelchair","disability"]},"woman-in-manual-wheelchair":{"a":"Woman in Manual Wheelchair","b":"1F469-200D-1F9BD","j":["accessibility","wheelchair","woman","disability"]},"person-running":{"a":"Person Running","b":"1F3C3","j":["marathon","running","move"]},"man-running":{"a":"Man Running","b":"1F3C3-200D-2642-FE0F","j":["man","marathon","racing","running","walking","exercise","race"]},"woman-running":{"a":"Woman Running","b":"1F3C3-200D-2640-FE0F","j":["marathon","racing","running","woman","walking","exercise","race","female"]},"woman-dancing":{"a":"Woman Dancing","b":"1F483","j":["dance","dancing","woman","female","girl","fun"]},"man-dancing":{"a":"Man Dancing","b":"1F57A","j":["dance","dancing","man","male","boy","fun","dancer"]},"person-in-suit-levitating":{"a":"Person in Suit Levitating","b":"1F574","j":["business","person","suit","man_in_suit_levitating","levitate","hover","jump"]},"people-with-bunny-ears":{"a":"People with Bunny Ears","b":"1F46F","j":["bunny ear","dancer","partying","perform","costume"]},"men-with-bunny-ears":{"a":"Men with Bunny Ears","b":"1F46F-200D-2642-FE0F","j":["bunny ear","dancer","men","partying","male","bunny","boys"]},"women-with-bunny-ears":{"a":"Women with Bunny Ears","b":"1F46F-200D-2640-FE0F","j":["bunny ear","dancer","partying","women","female","bunny","girls"]},"person-in-steamy-room":{"a":"Person in Steamy Room","b":"1F9D6","j":["sauna","steam room","hamam","steambath","relax","spa"]},"man-in-steamy-room":{"a":"Man in Steamy Room","b":"1F9D6-200D-2642-FE0F","j":["sauna","steam room","male","man","spa","steamroom"]},"woman-in-steamy-room":{"a":"Woman in Steamy Room","b":"1F9D6-200D-2640-FE0F","j":["sauna","steam room","female","woman","spa","steamroom"]},"person-climbing":{"a":"Person Climbing","b":"1F9D7","j":["climber","sport"]},"man-climbing":{"a":"Man Climbing","b":"1F9D7-200D-2642-FE0F","j":["climber","sports","hobby","man","male","rock"]},"woman-climbing":{"a":"Woman Climbing","b":"1F9D7-200D-2640-FE0F","j":["climber","sports","hobby","woman","female","rock"]},"person-fencing":{"a":"Person Fencing","b":"1F93A","j":["fencer","fencing","sword","sports"]},"horse-racing":{"a":"Horse Racing","b":"1F3C7","j":["horse","jockey","racehorse","racing","animal","betting","competition","gambling","luck"]},"skier":{"a":"Skier","b":"26F7","j":["ski","snow","sports","winter"]},"snowboarder":{"a":"Snowboarder","b":"1F3C2","j":["ski","snow","snowboard","sports","winter"]},"person-golfing":{"a":"Person Golfing","b":"1F3CC","j":["ball","golf","sports","business"]},"man-golfing":{"a":"Man Golfing","b":"1F3CC-FE0F-200D-2642-FE0F","j":["golf","man","sport"]},"woman-golfing":{"a":"Woman Golfing","b":"1F3CC-FE0F-200D-2640-FE0F","j":["golf","woman","sports","business","female"]},"person-surfing":{"a":"Person Surfing","b":"1F3C4","j":["surfing","sport","sea"]},"man-surfing":{"a":"Man Surfing","b":"1F3C4-200D-2642-FE0F","j":["man","surfing","sports","ocean","sea","summer","beach"]},"woman-surfing":{"a":"Woman Surfing","b":"1F3C4-200D-2640-FE0F","j":["surfing","woman","sports","ocean","sea","summer","beach","female"]},"person-rowing-boat":{"a":"Person Rowing Boat","b":"1F6A3","j":["boat","rowboat","sport","move"]},"man-rowing-boat":{"a":"Man Rowing Boat","b":"1F6A3-200D-2642-FE0F","j":["boat","man","rowboat","sports","hobby","water","ship"]},"woman-rowing-boat":{"a":"Woman Rowing Boat","b":"1F6A3-200D-2640-FE0F","j":["boat","rowboat","woman","sports","hobby","water","ship","female"]},"person-swimming":{"a":"Person Swimming","b":"1F3CA","j":["swim","sport","pool"]},"man-swimming":{"a":"Man Swimming","b":"1F3CA-200D-2642-FE0F","j":["man","swim","sports","exercise","human","athlete","water","summer"]},"woman-swimming":{"a":"Woman Swimming","b":"1F3CA-200D-2640-FE0F","j":["swim","woman","sports","exercise","human","athlete","water","summer","female"]},"person-bouncing-ball":{"a":"Person Bouncing Ball","b":"26F9","j":["ball","sports","human"]},"man-bouncing-ball":{"a":"Man Bouncing Ball","b":"26F9-FE0F-200D-2642-FE0F","j":["ball","man","sport"]},"woman-bouncing-ball":{"a":"Woman Bouncing Ball","b":"26F9-FE0F-200D-2640-FE0F","j":["ball","woman","sports","human","female"]},"person-lifting-weights":{"a":"Person Lifting Weights","b":"1F3CB","j":["lifter","weight","sports","training","exercise"]},"man-lifting-weights":{"a":"Man Lifting Weights","b":"1F3CB-FE0F-200D-2642-FE0F","j":["man","weight lifter","sport"]},"woman-lifting-weights":{"a":"Woman Lifting Weights","b":"1F3CB-FE0F-200D-2640-FE0F","j":["weight lifter","woman","sports","training","exercise","female"]},"person-biking":{"a":"Person Biking","b":"1F6B4","j":["bicycle","biking","cyclist","sport","move"]},"man-biking":{"a":"Man Biking","b":"1F6B4-200D-2642-FE0F","j":["bicycle","biking","cyclist","man","sports","bike","exercise","hipster"]},"woman-biking":{"a":"Woman Biking","b":"1F6B4-200D-2640-FE0F","j":["bicycle","biking","cyclist","woman","sports","bike","exercise","hipster","female"]},"person-mountain-biking":{"a":"Person Mountain Biking","b":"1F6B5","j":["bicycle","bicyclist","bike","cyclist","mountain","sport","move"]},"man-mountain-biking":{"a":"Man Mountain Biking","b":"1F6B5-200D-2642-FE0F","j":["bicycle","bike","cyclist","man","mountain","transportation","sports","human","race"]},"woman-mountain-biking":{"a":"Woman Mountain Biking","b":"1F6B5-200D-2640-FE0F","j":["bicycle","bike","biking","cyclist","mountain","woman","transportation","sports","human","race","female"]},"person-cartwheeling":{"a":"Person Cartwheeling","b":"1F938","j":["cartwheel","gymnastics","sport","gymnastic"]},"man-cartwheeling":{"a":"Man Cartwheeling","b":"1F938-200D-2642-FE0F","j":["cartwheel","gymnastics","man"]},"woman-cartwheeling":{"a":"Woman Cartwheeling","b":"1F938-200D-2640-FE0F","j":["cartwheel","gymnastics","woman"]},"people-wrestling":{"a":"People Wrestling","b":"1F93C","j":["wrestle","wrestler","sport"]},"men-wrestling":{"a":"Men Wrestling","b":"1F93C-200D-2642-FE0F","j":["men","wrestle","sports","wrestlers"]},"women-wrestling":{"a":"Women Wrestling","b":"1F93C-200D-2640-FE0F","j":["women","wrestle","sports","wrestlers"]},"person-playing-water-polo":{"a":"Person Playing Water Polo","b":"1F93D","j":["polo","water","sport"]},"man-playing-water-polo":{"a":"Man Playing Water Polo","b":"1F93D-200D-2642-FE0F","j":["man","water polo","sports","pool"]},"woman-playing-water-polo":{"a":"Woman Playing Water Polo","b":"1F93D-200D-2640-FE0F","j":["water polo","woman","sports","pool"]},"person-playing-handball":{"a":"Person Playing Handball","b":"1F93E","j":["ball","handball","sport"]},"man-playing-handball":{"a":"Man Playing Handball","b":"1F93E-200D-2642-FE0F","j":["handball","man","sports"]},"woman-playing-handball":{"a":"Woman Playing Handball","b":"1F93E-200D-2640-FE0F","j":["handball","woman","sports"]},"person-juggling":{"a":"Person Juggling","b":"1F939","j":["balance","juggle","multitask","skill","performance"]},"man-juggling":{"a":"Man Juggling","b":"1F939-200D-2642-FE0F","j":["juggling","man","multitask","juggle","balance","skill"]},"woman-juggling":{"a":"Woman Juggling","b":"1F939-200D-2640-FE0F","j":["juggling","multitask","woman","juggle","balance","skill"]},"person-in-lotus-position":{"a":"Person in Lotus Position","b":"1F9D8","j":["meditation","yoga","serenity","meditate"]},"man-in-lotus-position":{"a":"Man in Lotus Position","b":"1F9D8-200D-2642-FE0F","j":["meditation","yoga","man","male","serenity","zen","mindfulness"]},"woman-in-lotus-position":{"a":"Woman in Lotus Position","b":"1F9D8-200D-2640-FE0F","j":["meditation","yoga","woman","female","serenity","zen","mindfulness"]},"person-taking-bath":{"a":"Person Taking Bath","b":"1F6C0","j":["bath","bathtub","clean","shower","bathroom"]},"person-in-bed":{"a":"Person in Bed","b":"1F6CC","j":["hotel","sleep","bed","rest"]},"people-holding-hands":{"a":"People Holding Hands","b":"1F9D1-200D-1F91D-200D-1F9D1","j":["couple","hand","hold","holding hands","person","friendship"]},"women-holding-hands":{"a":"Women Holding Hands","b":"1F46D","j":["couple","hand","holding hands","women","pair","friendship","love","like","female","people","human"]},"woman-and-man-holding-hands":{"a":"Woman and Man Holding Hands","b":"1F46B","j":["couple","hand","hold","holding hands","man","woman","pair","people","human","love","date","dating","like","affection","valentines","marriage"]},"men-holding-hands":{"a":"Men Holding Hands","b":"1F46C","j":["couple","Gemini","holding hands","man","men","twins","zodiac","pair","love","like","bromance","friendship","people","human"]},"kiss":{"a":"Kiss","b":"1F48F","j":["couple","pair","valentines","love","like","dating","marriage"]},"kiss-woman-man":{"a":"Kiss: Woman, Man","b":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F468","j":["couple","kiss","man","woman","love"]},"kiss-man-man":{"a":"Kiss: Man, Man","b":"1F468-200D-2764-FE0F-200D-1F48B-200D-1F468","j":["couple","kiss","man","pair","valentines","love","like","dating","marriage"]},"kiss-woman-woman":{"a":"Kiss: Woman, Woman","b":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F469","j":["couple","kiss","woman","pair","valentines","love","like","dating","marriage"]},"couple-with-heart":{"a":"Couple with Heart","b":"1F491","j":["couple","love","pair","like","affection","human","dating","valentines","marriage"]},"couple-with-heart-woman-man":{"a":"Couple with Heart: Woman, Man","b":"1F469-200D-2764-FE0F-200D-1F468","j":["couple","couple with heart","love","man","woman"]},"couple-with-heart-man-man":{"a":"Couple with Heart: Man, Man","b":"1F468-200D-2764-FE0F-200D-1F468","j":["couple","couple with heart","love","man","pair","like","affection","human","dating","valentines","marriage"]},"couple-with-heart-woman-woman":{"a":"Couple with Heart: Woman, Woman","b":"1F469-200D-2764-FE0F-200D-1F469","j":["couple","couple with heart","love","woman","pair","like","affection","human","dating","valentines","marriage"]},"family":{"a":"Family","b":"1F46A","j":["home","parents","child","mom","dad","father","mother","people","human"]},"family-man-woman-boy":{"a":"Family: Man, Woman, Boy","b":"1F468-200D-1F469-200D-1F466","j":["boy","family","man","woman","love"]},"family-man-woman-girl":{"a":"Family: Man, Woman, Girl","b":"1F468-200D-1F469-200D-1F467","j":["family","girl","man","woman","home","parents","people","human","child"]},"family-man-woman-girl-boy":{"a":"Family: Man, Woman, Girl, Boy","b":"1F468-200D-1F469-200D-1F467-200D-1F466","j":["boy","family","girl","man","woman","home","parents","people","human","children"]},"family-man-woman-boy-boy":{"a":"Family: Man, Woman, Boy, Boy","b":"1F468-200D-1F469-200D-1F466-200D-1F466","j":["boy","family","man","woman","home","parents","people","human","children"]},"family-man-woman-girl-girl":{"a":"Family: Man, Woman, Girl, Girl","b":"1F468-200D-1F469-200D-1F467-200D-1F467","j":["family","girl","man","woman","home","parents","people","human","children"]},"family-man-man-boy":{"a":"Family: Man, Man, Boy","b":"1F468-200D-1F468-200D-1F466","j":["boy","family","man","home","parents","people","human","children"]},"family-man-man-girl":{"a":"Family: Man, Man, Girl","b":"1F468-200D-1F468-200D-1F467","j":["family","girl","man","home","parents","people","human","children"]},"family-man-man-girl-boy":{"a":"Family: Man, Man, Girl, Boy","b":"1F468-200D-1F468-200D-1F467-200D-1F466","j":["boy","family","girl","man","home","parents","people","human","children"]},"family-man-man-boy-boy":{"a":"Family: Man, Man, Boy, Boy","b":"1F468-200D-1F468-200D-1F466-200D-1F466","j":["boy","family","man","home","parents","people","human","children"]},"family-man-man-girl-girl":{"a":"Family: Man, Man, Girl, Girl","b":"1F468-200D-1F468-200D-1F467-200D-1F467","j":["family","girl","man","home","parents","people","human","children"]},"family-woman-woman-boy":{"a":"Family: Woman, Woman, Boy","b":"1F469-200D-1F469-200D-1F466","j":["boy","family","woman","home","parents","people","human","children"]},"family-woman-woman-girl":{"a":"Family: Woman, Woman, Girl","b":"1F469-200D-1F469-200D-1F467","j":["family","girl","woman","home","parents","people","human","children"]},"family-woman-woman-girl-boy":{"a":"Family: Woman, Woman, Girl, Boy","b":"1F469-200D-1F469-200D-1F467-200D-1F466","j":["boy","family","girl","woman","home","parents","people","human","children"]},"family-woman-woman-boy-boy":{"a":"Family: Woman, Woman, Boy, Boy","b":"1F469-200D-1F469-200D-1F466-200D-1F466","j":["boy","family","woman","home","parents","people","human","children"]},"family-woman-woman-girl-girl":{"a":"Family: Woman, Woman, Girl, Girl","b":"1F469-200D-1F469-200D-1F467-200D-1F467","j":["family","girl","woman","home","parents","people","human","children"]},"family-man-boy":{"a":"Family: Man, Boy","b":"1F468-200D-1F466","j":["boy","family","man","home","parent","people","human","child"]},"family-man-boy-boy":{"a":"Family: Man, Boy, Boy","b":"1F468-200D-1F466-200D-1F466","j":["boy","family","man","home","parent","people","human","children"]},"family-man-girl":{"a":"Family: Man, Girl","b":"1F468-200D-1F467","j":["family","girl","man","home","parent","people","human","child"]},"family-man-girl-boy":{"a":"Family: Man, Girl, Boy","b":"1F468-200D-1F467-200D-1F466","j":["boy","family","girl","man","home","parent","people","human","children"]},"family-man-girl-girl":{"a":"Family: Man, Girl, Girl","b":"1F468-200D-1F467-200D-1F467","j":["family","girl","man","home","parent","people","human","children"]},"family-woman-boy":{"a":"Family: Woman, Boy","b":"1F469-200D-1F466","j":["boy","family","woman","home","parent","people","human","child"]},"family-woman-boy-boy":{"a":"Family: Woman, Boy, Boy","b":"1F469-200D-1F466-200D-1F466","j":["boy","family","woman","home","parent","people","human","children"]},"family-woman-girl":{"a":"Family: Woman, Girl","b":"1F469-200D-1F467","j":["family","girl","woman","home","parent","people","human","child"]},"family-woman-girl-boy":{"a":"Family: Woman, Girl, Boy","b":"1F469-200D-1F467-200D-1F466","j":["boy","family","girl","woman","home","parent","people","human","children"]},"family-woman-girl-girl":{"a":"Family: Woman, Girl, Girl","b":"1F469-200D-1F467-200D-1F467","j":["family","girl","woman","home","parent","people","human","children"]},"speaking-head":{"a":"Speaking Head","b":"1F5E3","j":["face","head","silhouette","speak","speaking","user","person","human","sing","say","talk"]},"bust-in-silhouette":{"a":"Bust in Silhouette","b":"1F464","j":["bust","silhouette","user","person","human"]},"busts-in-silhouette":{"a":"Busts in Silhouette","b":"1F465","j":["bust","silhouette","user","person","human","group","team"]},"people-hugging":{"a":"People Hugging","b":"1FAC2","j":["goodbye","hello","hug","thanks","care"]},"footprints":{"a":"Footprints","b":"1F463","j":["clothing","footprint","print","feet","tracking","walking","beach"]},"red-hair":{"a":"Red Hair","b":"1F9B0","j":["ginger","red hair","redhead"]},"curly-hair":{"a":"Curly Hair","b":"1F9B1","j":["afro","curly","curly hair","ringlets"]},"white-hair":{"a":"White Hair","b":"1F9B3","j":["gray","hair","old","white"]},"bald":{"a":"Bald","b":"1F9B2","j":["bald","chemotherapy","hairless","no hair","shaven"]},"monkey-face":{"a":"Monkey Face","b":"1F435","j":["face","monkey","animal","nature","circus"]},"monkey":{"a":"Monkey","b":"1F412","j":["animal","nature","banana","circus"]},"gorilla":{"a":"Gorilla","b":"1F98D","j":["animal","nature","circus"]},"orangutan":{"a":"Orangutan","b":"1F9A7","j":["ape","animal"]},"dog-face":{"a":"Dog Face","b":"1F436","j":["dog","face","pet","animal","friend","nature","woof","puppy","faithful"]},"dog":{"a":"Dog","b":"1F415","j":["pet","animal","nature","friend","doge","faithful"]},"guide-dog":{"a":"Guide Dog","b":"1F9AE","j":["accessibility","blind","guide","animal"]},"service-dog":{"a":"Service Dog","b":"1F415-200D-1F9BA","j":["accessibility","assistance","dog","service","blind","animal"]},"poodle":{"a":"Poodle","b":"1F429","j":["dog","animal","101","nature","pet"]},"wolf":{"a":"Wolf","b":"1F43A","j":["face","animal","nature","wild"]},"fox":{"a":"Fox","b":"1F98A","j":["face","animal","nature"]},"raccoon":{"a":"Raccoon","b":"1F99D","j":["curious","sly","animal","nature"]},"cat-face":{"a":"Cat Face","b":"1F431","j":["cat","face","pet","animal","meow","nature","kitten"]},"cat":{"a":"Cat","b":"1F408","j":["pet","animal","meow","cats"]},"black-cat":{"a":"Black Cat","b":"1F408-200D-2B1B","j":["black","cat","unlucky","superstition","luck"]},"lion":{"a":"Lion","b":"1F981","j":["face","Leo","zodiac","animal","nature"]},"tiger-face":{"a":"Tiger Face","b":"1F42F","j":["face","tiger","animal","cat","danger","wild","nature","roar"]},"tiger":{"a":"Tiger","b":"1F405","j":["animal","nature","roar"]},"leopard":{"a":"Leopard","b":"1F406","j":["animal","nature"]},"horse-face":{"a":"Horse Face","b":"1F434","j":["face","horse","animal","brown","nature"]},"horse":{"a":"Horse","b":"1F40E","j":["equestrian","racehorse","racing","animal","gamble","luck"]},"unicorn":{"a":"Unicorn","b":"1F984","j":["face","animal","nature","mystical"]},"zebra":{"a":"Zebra","b":"1F993","j":["stripe","animal","nature","stripes","safari"]},"deer":{"a":"Deer","b":"1F98C","j":["animal","nature","horns","venison"]},"bison":{"a":"Bison","b":"1F9AC","j":["buffalo","herd","wisent","ox"]},"cow-face":{"a":"Cow Face","b":"1F42E","j":["cow","face","beef","ox","animal","nature","moo","milk"]},"ox":{"a":"Ox","b":"1F402","j":["bull","Taurus","zodiac","animal","cow","beef"]},"water-buffalo":{"a":"Water Buffalo","b":"1F403","j":["buffalo","water","animal","nature","ox","cow"]},"cow":{"a":"Cow","b":"1F404","j":["beef","ox","animal","nature","moo","milk"]},"pig-face":{"a":"Pig Face","b":"1F437","j":["face","pig","animal","oink","nature"]},"pig":{"a":"Pig","b":"1F416","j":["sow","animal","nature"]},"boar":{"a":"Boar","b":"1F417","j":["pig","animal","nature"]},"pig-nose":{"a":"Pig Nose","b":"1F43D","j":["face","nose","pig","animal","oink"]},"ram":{"a":"Ram","b":"1F40F","j":["Aries","male","sheep","zodiac","animal","nature"]},"ewe":{"a":"Ewe","b":"1F411","j":["female","sheep","animal","nature","wool","shipit"]},"goat":{"a":"Goat","b":"1F410","j":["Capricorn","zodiac","animal","nature"]},"camel":{"a":"Camel","b":"1F42A","j":["dromedary","hump","animal","hot","desert"]},"twohump-camel":{"a":"Two-Hump Camel","b":"1F42B","j":["bactrian","camel","hump","two-hump camel","two_hump_camel","animal","nature","hot","desert"]},"llama":{"a":"Llama","b":"1F999","j":["alpaca","guanaco","vicuña","wool","animal","nature"]},"giraffe":{"a":"Giraffe","b":"1F992","j":["spots","animal","nature","safari"]},"elephant":{"a":"Elephant","b":"1F418","j":["animal","nature","nose","th","circus"]},"mammoth":{"a":"Mammoth","b":"1F9A3","j":["extinction","large","tusk","woolly","elephant","tusks"]},"rhinoceros":{"a":"Rhinoceros","b":"1F98F","j":["animal","nature","horn"]},"hippopotamus":{"a":"Hippopotamus","b":"1F99B","j":["hippo","animal","nature"]},"mouse-face":{"a":"Mouse Face","b":"1F42D","j":["face","mouse","animal","nature","cheese_wedge","rodent"]},"mouse":{"a":"Mouse","b":"1F401","j":["animal","nature","rodent"]},"rat":{"a":"Rat","b":"1F400","j":["animal","mouse","rodent"]},"hamster":{"a":"Hamster","b":"1F439","j":["face","pet","animal","nature"]},"rabbit-face":{"a":"Rabbit Face","b":"1F430","j":["bunny","face","pet","rabbit","animal","nature","spring","magic"]},"rabbit":{"a":"Rabbit","b":"1F407","j":["bunny","pet","animal","nature","magic","spring"]},"chipmunk":{"a":"Chipmunk","b":"1F43F","j":["squirrel","animal","nature","rodent"]},"beaver":{"a":"Beaver","b":"1F9AB","j":["dam","animal","rodent"]},"hedgehog":{"a":"Hedgehog","b":"1F994","j":["spiny","animal","nature"]},"bat":{"a":"Bat","b":"1F987","j":["vampire","animal","nature","blind"]},"bear":{"a":"Bear","b":"1F43B","j":["face","animal","nature","wild"]},"polar-bear":{"a":"Polar Bear","b":"1F43B-200D-2744-FE0F","j":["arctic","bear","white","animal"]},"koala":{"a":"Koala","b":"1F428","j":["bear","animal","nature"]},"panda":{"a":"Panda","b":"1F43C","j":["face","animal","nature"]},"sloth":{"a":"Sloth","b":"1F9A5","j":["lazy","slow","animal"]},"otter":{"a":"Otter","b":"1F9A6","j":["fishing","playful","animal"]},"skunk":{"a":"Skunk","b":"1F9A8","j":["stink","animal"]},"kangaroo":{"a":"Kangaroo","b":"1F998","j":["Australia","joey","jump","marsupial","animal","nature","australia","hop"]},"badger":{"a":"Badger","b":"1F9A1","j":["honey badger","pester","animal","nature","honey"]},"paw-prints":{"a":"Paw Prints","b":"1F43E","j":["feet","paw","print","animal","tracking","footprints","dog","cat","pet"]},"turkey":{"a":"Turkey","b":"1F983","j":["bird","animal"]},"chicken":{"a":"Chicken","b":"1F414","j":["bird","animal","cluck","nature"]},"rooster":{"a":"Rooster","b":"1F413","j":["bird","animal","nature","chicken"]},"hatching-chick":{"a":"Hatching Chick","b":"1F423","j":["baby","bird","chick","hatching","animal","chicken","egg","born"]},"baby-chick":{"a":"Baby Chick","b":"1F424","j":["baby","bird","chick","animal","chicken"]},"frontfacing-baby-chick":{"a":"Front-Facing Baby Chick","b":"1F425","j":["baby","bird","chick","front-facing baby chick","front_facing_baby_chick","animal","chicken"]},"bird":{"a":"Bird","b":"1F426","j":["animal","nature","fly","tweet","spring"]},"penguin":{"a":"Penguin","b":"1F427","j":["bird","animal","nature"]},"dove":{"a":"Dove","b":"1F54A","j":["bird","fly","peace","animal"]},"eagle":{"a":"Eagle","b":"1F985","j":["bird","animal","nature"]},"duck":{"a":"Duck","b":"1F986","j":["bird","animal","nature","mallard"]},"swan":{"a":"Swan","b":"1F9A2","j":["bird","cygnet","ugly duckling","animal","nature"]},"owl":{"a":"Owl","b":"1F989","j":["bird","wise","animal","nature","hoot"]},"dodo":{"a":"Dodo","b":"1F9A4","j":["extinction","large","Mauritius","animal","bird"]},"feather":{"a":"Feather","b":"1FAB6","j":["bird","flight","light","plumage","fly"]},"flamingo":{"a":"Flamingo","b":"1F9A9","j":["flamboyant","tropical","animal"]},"peacock":{"a":"Peacock","b":"1F99A","j":["bird","ostentatious","peahen","proud","animal","nature"]},"parrot":{"a":"Parrot","b":"1F99C","j":["bird","pirate","talk","animal","nature"]},"frog":{"a":"Frog","b":"1F438","j":["face","animal","nature","croak","toad"]},"crocodile":{"a":"Crocodile","b":"1F40A","j":["animal","nature","reptile","lizard","alligator"]},"turtle":{"a":"Turtle","b":"1F422","j":["terrapin","tortoise","animal","slow","nature"]},"lizard":{"a":"Lizard","b":"1F98E","j":["reptile","animal","nature"]},"snake":{"a":"Snake","b":"1F40D","j":["bearer","Ophiuchus","serpent","zodiac","animal","evil","nature","hiss","python"]},"dragon-face":{"a":"Dragon Face","b":"1F432","j":["dragon","face","fairy tale","animal","myth","nature","chinese","green"]},"dragon":{"a":"Dragon","b":"1F409","j":["fairy tale","animal","myth","nature","chinese","green"]},"sauropod":{"a":"Sauropod","b":"1F995","j":["brachiosaurus","brontosaurus","diplodocus","animal","nature","dinosaur","extinct"]},"trex":{"a":"T-Rex","b":"1F996","j":["Tyrannosaurus Rex","t_rex","animal","nature","dinosaur","tyrannosaurus","extinct"]},"spouting-whale":{"a":"Spouting Whale","b":"1F433","j":["face","spouting","whale","animal","nature","sea","ocean"]},"whale":{"a":"Whale","b":"1F40B","j":["animal","nature","sea","ocean"]},"dolphin":{"a":"Dolphin","b":"1F42C","j":["flipper","animal","nature","fish","sea","ocean","fins","beach"]},"seal":{"a":"Seal","b":"1F9AD","j":["sea lion","animal","creature","sea"]},"fish":{"a":"Fish","b":"1F41F","j":["Pisces","zodiac","animal","food","nature"]},"tropical-fish":{"a":"Tropical Fish","b":"1F420","j":["fish","tropical","animal","swim","ocean","beach","nemo"]},"blowfish":{"a":"Blowfish","b":"1F421","j":["fish","animal","nature","food","sea","ocean"]},"shark":{"a":"Shark","b":"1F988","j":["fish","animal","nature","sea","ocean","jaws","fins","beach"]},"octopus":{"a":"Octopus","b":"1F419","j":["animal","creature","ocean","sea","nature","beach"]},"spiral-shell":{"a":"Spiral Shell","b":"1F41A","j":["shell","spiral","nature","sea","beach"]},"snail":{"a":"Snail","b":"1F40C","j":["slow","animal","shell"]},"butterfly":{"a":"Butterfly","b":"1F98B","j":["insect","pretty","animal","nature","caterpillar"]},"bug":{"a":"Bug","b":"1F41B","j":["insect","animal","nature","worm"]},"ant":{"a":"Ant","b":"1F41C","j":["insect","animal","nature","bug"]},"honeybee":{"a":"Honeybee","b":"1F41D","j":["bee","insect","animal","nature","bug","spring","honey"]},"beetle":{"a":"Beetle","b":"1FAB2","j":["bug","insect"]},"lady-beetle":{"a":"Lady Beetle","b":"1F41E","j":["beetle","insect","ladybird","ladybug","animal","nature"]},"cricket":{"a":"Cricket","b":"1F997","j":["grasshopper","Orthoptera","animal","chirp"]},"cockroach":{"a":"Cockroach","b":"1FAB3","j":["insect","pest","roach","pests"]},"spider":{"a":"Spider","b":"1F577","j":["insect","animal","arachnid"]},"spider-web":{"a":"Spider Web","b":"1F578","j":["spider","web","animal","insect","arachnid","silk"]},"scorpion":{"a":"Scorpion","b":"1F982","j":["scorpio","Scorpio","zodiac","animal","arachnid"]},"mosquito":{"a":"Mosquito","b":"1F99F","j":["disease","fever","malaria","pest","virus","animal","nature","insect"]},"fly":{"a":"Fly","b":"1FAB0","j":["disease","maggot","pest","rotting","insect"]},"worm":{"a":"Worm","b":"1FAB1","j":["annelid","earthworm","parasite","animal"]},"microbe":{"a":"Microbe","b":"1F9A0","j":["amoeba","bacteria","virus","germs"]},"bouquet":{"a":"Bouquet","b":"1F490","j":["flower","flowers","nature","spring"]},"cherry-blossom":{"a":"Cherry Blossom","b":"1F338","j":["blossom","cherry","flower","nature","plant","spring"]},"white-flower":{"a":"White Flower","b":"1F4AE","j":["flower","japanese","spring"]},"rosette":{"a":"Rosette","b":"1F3F5","j":["plant","flower","decoration","military"]},"rose":{"a":"Rose","b":"1F339","j":["flower","flowers","valentines","love","spring"]},"wilted-flower":{"a":"Wilted Flower","b":"1F940","j":["flower","wilted","plant","nature"]},"hibiscus":{"a":"Hibiscus","b":"1F33A","j":["flower","plant","vegetable","flowers","beach"]},"sunflower":{"a":"Sunflower","b":"1F33B","j":["flower","sun","nature","plant","fall"]},"blossom":{"a":"Blossom","b":"1F33C","j":["flower","nature","flowers","yellow"]},"tulip":{"a":"Tulip","b":"1F337","j":["flower","flowers","plant","nature","summer","spring"]},"seedling":{"a":"Seedling","b":"1F331","j":["young","plant","nature","grass","lawn","spring"]},"potted-plant":{"a":"Potted Plant","b":"1FAB4","j":["boring","grow","house","nurturing","plant","useless","greenery"]},"evergreen-tree":{"a":"Evergreen Tree","b":"1F332","j":["tree","plant","nature"]},"deciduous-tree":{"a":"Deciduous Tree","b":"1F333","j":["deciduous","shedding","tree","plant","nature"]},"palm-tree":{"a":"Palm Tree","b":"1F334","j":["palm","tree","plant","vegetable","nature","summer","beach","mojito","tropical"]},"cactus":{"a":"Cactus","b":"1F335","j":["plant","vegetable","nature"]},"sheaf-of-rice":{"a":"Sheaf of Rice","b":"1F33E","j":["ear","grain","rice","nature","plant"]},"herb":{"a":"Herb","b":"1F33F","j":["leaf","vegetable","plant","medicine","weed","grass","lawn"]},"shamrock":{"a":"Shamrock","b":"2618","j":["plant","vegetable","nature","irish","clover"]},"four-leaf-clover":{"a":"Four Leaf Clover","b":"1F340","j":["4","clover","four","four-leaf clover","leaf","vegetable","plant","nature","lucky","irish"]},"maple-leaf":{"a":"Maple Leaf","b":"1F341","j":["falling","leaf","maple","nature","plant","vegetable","ca","fall"]},"fallen-leaf":{"a":"Fallen Leaf","b":"1F342","j":["falling","leaf","nature","plant","vegetable","leaves"]},"leaf-fluttering-in-wind":{"a":"Leaf Fluttering in Wind","b":"1F343","j":["blow","flutter","leaf","wind","nature","plant","tree","vegetable","grass","lawn","spring"]},"grapes":{"a":"Grapes","b":"1F347","j":["fruit","grape","food","wine"]},"melon":{"a":"Melon","b":"1F348","j":["fruit","nature","food"]},"watermelon":{"a":"Watermelon","b":"1F349","j":["fruit","food","picnic","summer"]},"tangerine":{"a":"Tangerine","b":"1F34A","j":["fruit","orange","food","nature"]},"lemon":{"a":"Lemon","b":"1F34B","j":["citrus","fruit","nature"]},"banana":{"a":"Banana","b":"1F34C","j":["fruit","food","monkey"]},"pineapple":{"a":"Pineapple","b":"1F34D","j":["fruit","nature","food"]},"mango":{"a":"Mango","b":"1F96D","j":["fruit","tropical","food"]},"red-apple":{"a":"Red Apple","b":"1F34E","j":["apple","fruit","red","mac","school"]},"green-apple":{"a":"Green Apple","b":"1F34F","j":["apple","fruit","green","nature"]},"pear":{"a":"Pear","b":"1F350","j":["fruit","nature","food"]},"peach":{"a":"Peach","b":"1F351","j":["fruit","nature","food"]},"cherries":{"a":"Cherries","b":"1F352","j":["berries","cherry","fruit","red","food"]},"strawberry":{"a":"Strawberry","b":"1F353","j":["berry","fruit","food","nature"]},"blueberries":{"a":"Blueberries","b":"1FAD0","j":["berry","bilberry","blue","blueberry","fruit"]},"kiwi-fruit":{"a":"Kiwi Fruit","b":"1F95D","j":["food","fruit","kiwi"]},"tomato":{"a":"Tomato","b":"1F345","j":["fruit","vegetable","nature","food"]},"olive":{"a":"Olive","b":"1FAD2","j":["food","fruit"]},"coconut":{"a":"Coconut","b":"1F965","j":["palm","piña colada","fruit","nature","food"]},"avocado":{"a":"Avocado","b":"1F951","j":["food","fruit"]},"eggplant":{"a":"Eggplant","b":"1F346","j":["aubergine","vegetable","nature","food"]},"potato":{"a":"Potato","b":"1F954","j":["food","vegetable","tuber","vegatable","starch"]},"carrot":{"a":"Carrot","b":"1F955","j":["food","vegetable","orange"]},"ear-of-corn":{"a":"Ear of Corn","b":"1F33D","j":["corn","ear","maize","maze","food","vegetable","plant"]},"hot-pepper":{"a":"Hot Pepper","b":"1F336","j":["hot","pepper","food","spicy","chilli","chili"]},"bell-pepper":{"a":"Bell Pepper","b":"1FAD1","j":["capsicum","pepper","vegetable","fruit","plant"]},"cucumber":{"a":"Cucumber","b":"1F952","j":["food","pickle","vegetable","fruit"]},"leafy-green":{"a":"Leafy Green","b":"1F96C","j":["bok choy","cabbage","kale","lettuce","food","vegetable","plant"]},"broccoli":{"a":"Broccoli","b":"1F966","j":["wild cabbage","fruit","food","vegetable"]},"garlic":{"a":"Garlic","b":"1F9C4","j":["flavoring","food","spice","cook"]},"onion":{"a":"Onion","b":"1F9C5","j":["flavoring","cook","food","spice"]},"mushroom":{"a":"Mushroom","b":"1F344","j":["toadstool","plant","vegetable"]},"peanuts":{"a":"Peanuts","b":"1F95C","j":["food","nut","peanut","vegetable"]},"chestnut":{"a":"Chestnut","b":"1F330","j":["plant","food","squirrel"]},"bread":{"a":"Bread","b":"1F35E","j":["loaf","food","wheat","breakfast","toast"]},"croissant":{"a":"Croissant","b":"1F950","j":["bread","breakfast","food","french","roll"]},"baguette-bread":{"a":"Baguette Bread","b":"1F956","j":["baguette","bread","food","french"]},"flatbread":{"a":"Flatbread","b":"1FAD3","j":["arepa","lavash","naan","pita","flour","food"]},"pretzel":{"a":"Pretzel","b":"1F968","j":["twisted","convoluted","food","bread"]},"bagel":{"a":"Bagel","b":"1F96F","j":["bakery","breakfast","schmear","food","bread"]},"pancakes":{"a":"Pancakes","b":"1F95E","j":["breakfast","crêpe","food","hotcake","pancake","flapjacks","hotcakes"]},"waffle":{"a":"Waffle","b":"1F9C7","j":["breakfast","indecisive","iron","food"]},"cheese-wedge":{"a":"Cheese Wedge","b":"1F9C0","j":["cheese","food","chadder"]},"meat-on-bone":{"a":"Meat on Bone","b":"1F356","j":["bone","meat","good","food","drumstick"]},"poultry-leg":{"a":"Poultry Leg","b":"1F357","j":["bone","chicken","drumstick","leg","poultry","food","meat","bird","turkey"]},"cut-of-meat":{"a":"Cut of Meat","b":"1F969","j":["chop","lambchop","porkchop","steak","food","cow","meat","cut"]},"bacon":{"a":"Bacon","b":"1F953","j":["breakfast","food","meat","pork","pig"]},"hamburger":{"a":"Hamburger","b":"1F354","j":["burger","meat","fast food","beef","cheeseburger","mcdonalds","burger king"]},"french-fries":{"a":"French Fries","b":"1F35F","j":["french","fries","chips","snack","fast food"]},"pizza":{"a":"Pizza","b":"1F355","j":["cheese","slice","food","party"]},"hot-dog":{"a":"Hot Dog","b":"1F32D","j":["frankfurter","hotdog","sausage","food"]},"sandwich":{"a":"Sandwich","b":"1F96A","j":["bread","food","lunch"]},"taco":{"a":"Taco","b":"1F32E","j":["mexican","food"]},"burrito":{"a":"Burrito","b":"1F32F","j":["mexican","wrap","food"]},"tamale":{"a":"Tamale","b":"1FAD4","j":["mexican","wrapped","food","masa"]},"stuffed-flatbread":{"a":"Stuffed Flatbread","b":"1F959","j":["falafel","flatbread","food","gyro","kebab","stuffed"]},"falafel":{"a":"Falafel","b":"1F9C6","j":["chickpea","meatball","food"]},"egg":{"a":"Egg","b":"1F95A","j":["breakfast","food","chicken"]},"cooking":{"a":"Cooking","b":"1F373","j":["breakfast","egg","frying","pan","food","kitchen"]},"shallow-pan-of-food":{"a":"Shallow Pan of Food","b":"1F958","j":["casserole","food","paella","pan","shallow","cooking"]},"pot-of-food":{"a":"Pot of Food","b":"1F372","j":["pot","stew","food","meat","soup"]},"fondue":{"a":"Fondue","b":"1FAD5","j":["cheese","chocolate","melted","pot","Swiss","food"]},"bowl-with-spoon":{"a":"Bowl with Spoon","b":"1F963","j":["breakfast","cereal","congee","oatmeal","porridge","food"]},"green-salad":{"a":"Green Salad","b":"1F957","j":["food","green","salad","healthy","lettuce"]},"popcorn":{"a":"Popcorn","b":"1F37F","j":["food","movie theater","films","snack"]},"butter":{"a":"Butter","b":"1F9C8","j":["dairy","food","cook"]},"salt":{"a":"Salt","b":"1F9C2","j":["condiment","shaker"]},"canned-food":{"a":"Canned Food","b":"1F96B","j":["can","food","soup"]},"bento-box":{"a":"Bento Box","b":"1F371","j":["bento","box","food","japanese"]},"rice-cracker":{"a":"Rice Cracker","b":"1F358","j":["cracker","rice","food","japanese"]},"rice-ball":{"a":"Rice Ball","b":"1F359","j":["ball","Japanese","rice","food","japanese"]},"cooked-rice":{"a":"Cooked Rice","b":"1F35A","j":["cooked","rice","food","china","asian"]},"curry-rice":{"a":"Curry Rice","b":"1F35B","j":["curry","rice","food","spicy","hot","indian"]},"steaming-bowl":{"a":"Steaming Bowl","b":"1F35C","j":["bowl","noodle","ramen","steaming","food","japanese","chopsticks"]},"spaghetti":{"a":"Spaghetti","b":"1F35D","j":["pasta","food","italian","noodle"]},"roasted-sweet-potato":{"a":"Roasted Sweet Potato","b":"1F360","j":["potato","roasted","sweet","food","nature"]},"oden":{"a":"Oden","b":"1F362","j":["kebab","seafood","skewer","stick","food","japanese"]},"sushi":{"a":"Sushi","b":"1F363","j":["food","fish","japanese","rice"]},"fried-shrimp":{"a":"Fried Shrimp","b":"1F364","j":["fried","prawn","shrimp","tempura","food","animal","appetizer","summer"]},"fish-cake-with-swirl":{"a":"Fish Cake with Swirl","b":"1F365","j":["cake","fish","pastry","swirl","food","japan","sea","beach","narutomaki","pink","kamaboko","surimi","ramen"]},"moon-cake":{"a":"Moon Cake","b":"1F96E","j":["autumn","festival","yuèbǐng","food"]},"dango":{"a":"Dango","b":"1F361","j":["dessert","Japanese","skewer","stick","sweet","food","japanese","barbecue","meat"]},"dumpling":{"a":"Dumpling","b":"1F95F","j":["empanada","gyōza","jiaozi","pierogi","potsticker","food"]},"fortune-cookie":{"a":"Fortune Cookie","b":"1F960","j":["prophecy","food"]},"takeout-box":{"a":"Takeout Box","b":"1F961","j":["oyster pail","food","leftovers"]},"crab":{"a":"Crab","b":"1F980","j":["Cancer","zodiac","animal","crustacean"]},"lobster":{"a":"Lobster","b":"1F99E","j":["bisque","claws","seafood","animal","nature"]},"shrimp":{"a":"Shrimp","b":"1F990","j":["food","shellfish","small","animal","ocean","nature","seafood"]},"squid":{"a":"Squid","b":"1F991","j":["food","molusc","animal","nature","ocean","sea"]},"oyster":{"a":"Oyster","b":"1F9AA","j":["diving","pearl","food"]},"soft-ice-cream":{"a":"Soft Ice Cream","b":"1F366","j":["cream","dessert","ice","icecream","soft","sweet","food","hot","summer"]},"shaved-ice":{"a":"Shaved Ice","b":"1F367","j":["dessert","ice","shaved","sweet","hot","summer"]},"ice-cream":{"a":"Ice Cream","b":"1F368","j":["cream","dessert","ice","sweet","food","hot"]},"doughnut":{"a":"Doughnut","b":"1F369","j":["breakfast","dessert","donut","sweet","food","snack"]},"cookie":{"a":"Cookie","b":"1F36A","j":["dessert","sweet","food","snack","oreo","chocolate"]},"birthday-cake":{"a":"Birthday Cake","b":"1F382","j":["birthday","cake","celebration","dessert","pastry","sweet","food"]},"shortcake":{"a":"Shortcake","b":"1F370","j":["cake","dessert","pastry","slice","sweet","food"]},"cupcake":{"a":"Cupcake","b":"1F9C1","j":["bakery","sweet","food","dessert"]},"pie":{"a":"Pie","b":"1F967","j":["filling","pastry","fruit","meat","food","dessert"]},"chocolate-bar":{"a":"Chocolate Bar","b":"1F36B","j":["bar","chocolate","dessert","sweet","food","snack"]},"candy":{"a":"Candy","b":"1F36C","j":["dessert","sweet","snack","lolly"]},"lollipop":{"a":"Lollipop","b":"1F36D","j":["candy","dessert","sweet","food","snack"]},"custard":{"a":"Custard","b":"1F36E","j":["dessert","pudding","sweet","food"]},"honey-pot":{"a":"Honey Pot","b":"1F36F","j":["honey","honeypot","pot","sweet","bees","kitchen"]},"baby-bottle":{"a":"Baby Bottle","b":"1F37C","j":["baby","bottle","drink","milk","food","container"]},"glass-of-milk":{"a":"Glass of Milk","b":"1F95B","j":["drink","glass","milk","beverage","cow"]},"hot-beverage":{"a":"Hot Beverage","b":"2615","j":["beverage","coffee","drink","hot","steaming","tea","caffeine","latte","espresso"]},"teapot":{"a":"Teapot","b":"1FAD6","j":["drink","pot","tea","hot"]},"teacup-without-handle":{"a":"Teacup Without Handle","b":"1F375","j":["beverage","cup","drink","tea","teacup","bowl","breakfast","green","british"]},"sake":{"a":"Sake","b":"1F376","j":["bar","beverage","bottle","cup","drink","wine","drunk","japanese","alcohol","booze"]},"bottle-with-popping-cork":{"a":"Bottle with Popping Cork","b":"1F37E","j":["bar","bottle","cork","drink","popping","wine","celebration"]},"wine-glass":{"a":"Wine Glass","b":"1F377","j":["bar","beverage","drink","glass","wine","drunk","alcohol","booze"]},"cocktail-glass":{"a":"Cocktail Glass","b":"1F378","j":["bar","cocktail","drink","glass","drunk","alcohol","beverage","booze","mojito"]},"tropical-drink":{"a":"Tropical Drink","b":"1F379","j":["bar","drink","tropical","beverage","cocktail","summer","beach","alcohol","booze","mojito"]},"beer-mug":{"a":"Beer Mug","b":"1F37A","j":["bar","beer","drink","mug","relax","beverage","drunk","party","pub","summer","alcohol","booze"]},"clinking-beer-mugs":{"a":"Clinking Beer Mugs","b":"1F37B","j":["bar","beer","clink","drink","mug","relax","beverage","drunk","party","pub","summer","alcohol","booze"]},"clinking-glasses":{"a":"Clinking Glasses","b":"1F942","j":["celebrate","clink","drink","glass","beverage","party","alcohol","cheers","wine","champagne","toast"]},"tumbler-glass":{"a":"Tumbler Glass","b":"1F943","j":["glass","liquor","shot","tumbler","whisky","drink","beverage","drunk","alcohol","booze","bourbon","scotch"]},"cup-with-straw":{"a":"Cup with Straw","b":"1F964","j":["juice","soda","malt","soft drink","water","drink"]},"bubble-tea":{"a":"Bubble Tea","b":"1F9CB","j":["bubble","milk","pearl","tea","taiwan","boba","milk tea","straw"]},"beverage-box":{"a":"Beverage Box","b":"1F9C3","j":["beverage","box","juice","straw","sweet","drink"]},"mate":{"a":"Mate","b":"1F9C9","j":["drink","tea","beverage"]},"ice":{"a":"Ice","b":"1F9CA","j":["cold","ice cube","iceberg","water"]},"chopsticks":{"a":"Chopsticks","b":"1F962","j":["hashi","jeotgarak","kuaizi","food"]},"fork-and-knife-with-plate":{"a":"Fork and Knife with Plate","b":"1F37D","j":["cooking","fork","knife","plate","food","eat","meal","lunch","dinner","restaurant"]},"fork-and-knife":{"a":"Fork and Knife","b":"1F374","j":["cooking","cutlery","fork","knife","kitchen"]},"spoon":{"a":"Spoon","b":"1F944","j":["tableware","cutlery","kitchen"]},"kitchen-knife":{"a":"Kitchen Knife","b":"1F52A","j":["cooking","hocho","knife","tool","weapon","blade","cutlery","kitchen"]},"amphora":{"a":"Amphora","b":"1F3FA","j":["Aquarius","cooking","drink","jug","zodiac","vase","jar"]},"globe-showing-europeafrica":{"a":"Globe Showing Europe-Africa","b":"1F30D","j":["Africa","earth","Europe","globe","globe showing Europe-Africa","world","globe_showing_europe_africa","international"]},"globe-showing-americas":{"a":"Globe Showing Americas","b":"1F30E","j":["Americas","earth","globe","globe showing Americas","world","USA","international"]},"globe-showing-asiaaustralia":{"a":"Globe Showing Asia-Australia","b":"1F30F","j":["Asia","Australia","earth","globe","globe showing Asia-Australia","world","globe_showing_asia_australia","east","international"]},"globe-with-meridians":{"a":"Globe with Meridians","b":"1F310","j":["earth","globe","meridians","world","international","internet","interweb","i18n"]},"world-map":{"a":"World Map","b":"1F5FA","j":["map","world","location","direction"]},"map-of-japan":{"a":"Map of Japan","b":"1F5FE","j":["Japan","map","map of Japan","nation","country","japanese","asia"]},"compass":{"a":"Compass","b":"1F9ED","j":["magnetic","navigation","orienteering"]},"snowcapped-mountain":{"a":"Snow-Capped Mountain","b":"1F3D4","j":["cold","mountain","snow","snow-capped mountain","snow_capped_mountain","photo","nature","environment","winter"]},"mountain":{"a":"Mountain","b":"26F0","j":["photo","nature","environment"]},"volcano":{"a":"Volcano","b":"1F30B","j":["eruption","mountain","photo","nature","disaster"]},"mount-fuji":{"a":"Mount Fuji","b":"1F5FB","j":["fuji","mountain","photo","nature","japanese"]},"camping":{"a":"Camping","b":"1F3D5","j":["photo","outdoors","tent"]},"beach-with-umbrella":{"a":"Beach with Umbrella","b":"1F3D6","j":["beach","umbrella","weather","summer","sunny","sand","mojito"]},"desert":{"a":"Desert","b":"1F3DC","j":["photo","warm","saharah"]},"desert-island":{"a":"Desert Island","b":"1F3DD","j":["desert","island","photo","tropical","mojito"]},"national-park":{"a":"National Park","b":"1F3DE","j":["park","photo","environment","nature"]},"stadium":{"a":"Stadium","b":"1F3DF","j":["photo","place","sports","concert","venue"]},"classical-building":{"a":"Classical Building","b":"1F3DB","j":["classical","art","culture","history"]},"building-construction":{"a":"Building Construction","b":"1F3D7","j":["construction","wip","working","progress"]},"brick":{"a":"Brick","b":"1F9F1","j":["bricks","clay","mortar","wall"]},"rock":{"a":"Rock","b":"1FAA8","j":["boulder","heavy","solid","stone"]},"wood":{"a":"Wood","b":"1FAB5","j":["log","lumber","timber","nature","trunk"]},"hut":{"a":"Hut","b":"1F6D6","j":["house","roundhouse","yurt","structure"]},"houses":{"a":"Houses","b":"1F3D8","j":["buildings","photo"]},"derelict-house":{"a":"Derelict House","b":"1F3DA","j":["derelict","house","abandon","evict","broken","building"]},"house":{"a":"House","b":"1F3E0","j":["home","building"]},"house-with-garden":{"a":"House with Garden","b":"1F3E1","j":["garden","home","house","plant","nature"]},"office-building":{"a":"Office Building","b":"1F3E2","j":["building","bureau","work"]},"japanese-post-office":{"a":"Japanese Post Office","b":"1F3E3","j":["Japanese","Japanese post office","post","building","envelope","communication"]},"post-office":{"a":"Post Office","b":"1F3E4","j":["European","post","building","email"]},"hospital":{"a":"Hospital","b":"1F3E5","j":["doctor","medicine","building","health","surgery"]},"bank":{"a":"Bank","b":"1F3E6","j":["building","money","sales","cash","business","enterprise"]},"hotel":{"a":"Hotel","b":"1F3E8","j":["building","accomodation","checkin"]},"love-hotel":{"a":"Love Hotel","b":"1F3E9","j":["hotel","love","like","affection","dating"]},"convenience-store":{"a":"Convenience Store","b":"1F3EA","j":["convenience","store","building","shopping","groceries"]},"school":{"a":"School","b":"1F3EB","j":["building","student","education","learn","teach"]},"department-store":{"a":"Department Store","b":"1F3EC","j":["department","store","building","shopping","mall"]},"factory":{"a":"Factory","b":"1F3ED","j":["building","industry","pollution","smoke"]},"japanese-castle":{"a":"Japanese Castle","b":"1F3EF","j":["castle","Japanese","photo","building"]},"castle":{"a":"Castle","b":"1F3F0","j":["European","building","royalty","history"]},"wedding":{"a":"Wedding","b":"1F492","j":["chapel","romance","love","like","affection","couple","marriage","bride","groom"]},"tokyo-tower":{"a":"Tokyo Tower","b":"1F5FC","j":["Tokyo","tower","photo","japanese"]},"statue-of-liberty":{"a":"Statue of Liberty","b":"1F5FD","j":["liberty","statue","american","newyork"]},"church":{"a":"Church","b":"26EA","j":["Christian","cross","religion","building","christ"]},"mosque":{"a":"Mosque","b":"1F54C","j":["islam","Muslim","religion","worship","minaret"]},"hindu-temple":{"a":"Hindu Temple","b":"1F6D5","j":["hindu","temple","religion"]},"synagogue":{"a":"Synagogue","b":"1F54D","j":["Jew","Jewish","religion","temple","judaism","worship","jewish"]},"shinto-shrine":{"a":"Shinto Shrine","b":"26E9","j":["religion","shinto","shrine","temple","japan","kyoto"]},"kaaba":{"a":"Kaaba","b":"1F54B","j":["islam","Muslim","religion","mecca","mosque"]},"fountain":{"a":"Fountain","b":"26F2","j":["photo","summer","water","fresh"]},"tent":{"a":"Tent","b":"26FA","j":["camping","photo","outdoors"]},"foggy":{"a":"Foggy","b":"1F301","j":["fog","photo","mountain"]},"night-with-stars":{"a":"Night with Stars","b":"1F303","j":["night","star","evening","city","downtown"]},"cityscape":{"a":"Cityscape","b":"1F3D9","j":["city","photo","night life","urban"]},"sunrise-over-mountains":{"a":"Sunrise over Mountains","b":"1F304","j":["morning","mountain","sun","sunrise","view","vacation","photo"]},"sunrise":{"a":"Sunrise","b":"1F305","j":["morning","sun","view","vacation","photo"]},"cityscape-at-dusk":{"a":"Cityscape at Dusk","b":"1F306","j":["city","dusk","evening","landscape","sunset","photo","sky","buildings"]},"sunset":{"a":"Sunset","b":"1F307","j":["dusk","sun","photo","good morning","dawn"]},"bridge-at-night":{"a":"Bridge at Night","b":"1F309","j":["bridge","night","photo","sanfrancisco"]},"hot-springs":{"a":"Hot Springs","b":"2668","j":["hot","hotsprings","springs","steaming","bath","warm","relax"]},"carousel-horse":{"a":"Carousel Horse","b":"1F3A0","j":["carousel","horse","photo","carnival"]},"ferris-wheel":{"a":"Ferris Wheel","b":"1F3A1","j":["amusement park","ferris","wheel","photo","carnival","londoneye"]},"roller-coaster":{"a":"Roller Coaster","b":"1F3A2","j":["amusement park","coaster","roller","carnival","playground","photo","fun"]},"barber-pole":{"a":"Barber Pole","b":"1F488","j":["barber","haircut","pole","hair","salon","style"]},"circus-tent":{"a":"Circus Tent","b":"1F3AA","j":["circus","tent","festival","carnival","party"]},"locomotive":{"a":"Locomotive","b":"1F682","j":["engine","railway","steam","train","transportation","vehicle"]},"railway-car":{"a":"Railway Car","b":"1F683","j":["car","electric","railway","train","tram","trolleybus","transportation","vehicle"]},"highspeed-train":{"a":"High-Speed Train","b":"1F684","j":["high-speed train","railway","shinkansen","speed","train","high_speed_train","transportation","vehicle"]},"bullet-train":{"a":"Bullet Train","b":"1F685","j":["bullet","railway","shinkansen","speed","train","transportation","vehicle","fast","public","travel"]},"train":{"a":"Train","b":"1F686","j":["railway","transportation","vehicle"]},"metro":{"a":"Metro","b":"1F687","j":["subway","transportation","blue-square","mrt","underground","tube"]},"light-rail":{"a":"Light Rail","b":"1F688","j":["railway","transportation","vehicle"]},"station":{"a":"Station","b":"1F689","j":["railway","train","transportation","vehicle","public"]},"tram":{"a":"Tram","b":"1F68A","j":["trolleybus","transportation","vehicle"]},"monorail":{"a":"Monorail","b":"1F69D","j":["vehicle","transportation"]},"mountain-railway":{"a":"Mountain Railway","b":"1F69E","j":["car","mountain","railway","transportation","vehicle"]},"tram-car":{"a":"Tram Car","b":"1F68B","j":["car","tram","trolleybus","transportation","vehicle","carriage","public","travel"]},"bus":{"a":"Bus","b":"1F68C","j":["vehicle","car","transportation"]},"oncoming-bus":{"a":"Oncoming Bus","b":"1F68D","j":["bus","oncoming","vehicle","transportation"]},"trolleybus":{"a":"Trolleybus","b":"1F68E","j":["bus","tram","trolley","bart","transportation","vehicle"]},"minibus":{"a":"Minibus","b":"1F690","j":["bus","vehicle","car","transportation"]},"ambulance":{"a":"Ambulance","b":"1F691","j":["vehicle","health","911","hospital"]},"fire-engine":{"a":"Fire Engine","b":"1F692","j":["engine","fire","truck","transportation","cars","vehicle"]},"police-car":{"a":"Police Car","b":"1F693","j":["car","patrol","police","vehicle","cars","transportation","law","legal","enforcement"]},"oncoming-police-car":{"a":"Oncoming Police Car","b":"1F694","j":["car","oncoming","police","vehicle","law","legal","enforcement","911"]},"taxi":{"a":"Taxi","b":"1F695","j":["vehicle","uber","cars","transportation"]},"oncoming-taxi":{"a":"Oncoming Taxi","b":"1F696","j":["oncoming","taxi","vehicle","cars","uber"]},"automobile":{"a":"Automobile","b":"1F697","j":["car","red","transportation","vehicle"]},"oncoming-automobile":{"a":"Oncoming Automobile","b":"1F698","j":["automobile","car","oncoming","vehicle","transportation"]},"sport-utility-vehicle":{"a":"Sport Utility Vehicle","b":"1F699","j":["recreational","sport utility","transportation","vehicle"]},"pickup-truck":{"a":"Pickup Truck","b":"1F6FB","j":["pick-up","pickup","truck","car","transportation"]},"delivery-truck":{"a":"Delivery Truck","b":"1F69A","j":["delivery","truck","cars","transportation"]},"articulated-lorry":{"a":"Articulated Lorry","b":"1F69B","j":["lorry","semi","truck","vehicle","cars","transportation","express"]},"tractor":{"a":"Tractor","b":"1F69C","j":["vehicle","car","farming","agriculture"]},"racing-car":{"a":"Racing Car","b":"1F3CE","j":["car","racing","sports","race","fast","formula","f1"]},"motorcycle":{"a":"Motorcycle","b":"1F3CD","j":["racing","race","sports","fast"]},"motor-scooter":{"a":"Motor Scooter","b":"1F6F5","j":["motor","scooter","vehicle","vespa","sasha"]},"manual-wheelchair":{"a":"Manual Wheelchair","b":"1F9BD","j":["accessibility"]},"motorized-wheelchair":{"a":"Motorized Wheelchair","b":"1F9BC","j":["accessibility"]},"auto-rickshaw":{"a":"Auto Rickshaw","b":"1F6FA","j":["tuk tuk","move","transportation"]},"bicycle":{"a":"Bicycle","b":"1F6B2","j":["bike","sports","exercise","hipster"]},"kick-scooter":{"a":"Kick Scooter","b":"1F6F4","j":["kick","scooter","vehicle","razor"]},"skateboard":{"a":"Skateboard","b":"1F6F9","j":["board"]},"roller-skate":{"a":"Roller Skate","b":"1F6FC","j":["roller","skate","footwear","sports"]},"bus-stop":{"a":"Bus Stop","b":"1F68F","j":["bus","busstop","stop","transportation","wait"]},"motorway":{"a":"Motorway","b":"1F6E3","j":["highway","road","cupertino","interstate"]},"railway-track":{"a":"Railway Track","b":"1F6E4","j":["railway","train","transportation"]},"oil-drum":{"a":"Oil Drum","b":"1F6E2","j":["drum","oil","barrell"]},"fuel-pump":{"a":"Fuel Pump","b":"26FD","j":["diesel","fuel","fuelpump","gas","pump","station","gas station","petroleum"]},"police-car-light":{"a":"Police Car Light","b":"1F6A8","j":["beacon","car","light","police","revolving","ambulance","911","emergency","alert","error","pinged","law","legal"]},"horizontal-traffic-light":{"a":"Horizontal Traffic Light","b":"1F6A5","j":["light","signal","traffic","transportation"]},"vertical-traffic-light":{"a":"Vertical Traffic Light","b":"1F6A6","j":["light","signal","traffic","transportation","driving"]},"stop-sign":{"a":"Stop Sign","b":"1F6D1","j":["octagonal","sign","stop"]},"construction":{"a":"Construction","b":"1F6A7","j":["barrier","wip","progress","caution","warning"]},"anchor":{"a":"Anchor","b":"2693","j":["ship","tool","ferry","sea","boat"]},"sailboat":{"a":"Sailboat","b":"26F5","j":["boat","resort","sea","yacht","ship","summer","transportation","water","sailing"]},"canoe":{"a":"Canoe","b":"1F6F6","j":["boat","paddle","water","ship"]},"speedboat":{"a":"Speedboat","b":"1F6A4","j":["boat","ship","transportation","vehicle","summer"]},"passenger-ship":{"a":"Passenger Ship","b":"1F6F3","j":["passenger","ship","yacht","cruise","ferry"]},"ferry":{"a":"Ferry","b":"26F4","j":["boat","passenger","ship","yacht"]},"motor-boat":{"a":"Motor Boat","b":"1F6E5","j":["boat","motorboat","ship"]},"ship":{"a":"Ship","b":"1F6A2","j":["boat","passenger","transportation","titanic","deploy"]},"airplane":{"a":"Airplane","b":"2708","j":["aeroplane","vehicle","transportation","flight","fly"]},"small-airplane":{"a":"Small Airplane","b":"1F6E9","j":["aeroplane","airplane","flight","transportation","fly","vehicle"]},"airplane-departure":{"a":"Airplane Departure","b":"1F6EB","j":["aeroplane","airplane","check-in","departure","departures","airport","flight","landing"]},"airplane-arrival":{"a":"Airplane Arrival","b":"1F6EC","j":["aeroplane","airplane","arrivals","arriving","landing","airport","flight","boarding"]},"parachute":{"a":"Parachute","b":"1FA82","j":["hang-glide","parasail","skydive","fly","glide"]},"seat":{"a":"Seat","b":"1F4BA","j":["chair","sit","airplane","transport","bus","flight","fly"]},"helicopter":{"a":"Helicopter","b":"1F681","j":["vehicle","transportation","fly"]},"suspension-railway":{"a":"Suspension Railway","b":"1F69F","j":["railway","suspension","vehicle","transportation"]},"mountain-cableway":{"a":"Mountain Cableway","b":"1F6A0","j":["cable","gondola","mountain","transportation","vehicle","ski"]},"aerial-tramway":{"a":"Aerial Tramway","b":"1F6A1","j":["aerial","cable","car","gondola","tramway","transportation","vehicle","ski"]},"satellite":{"a":"Satellite","b":"1F6F0","j":["space","communication","gps","orbit","spaceflight","NASA","ISS"]},"rocket":{"a":"Rocket","b":"1F680","j":["space","launch","ship","staffmode","NASA","outer space","outer_space","fly"]},"flying-saucer":{"a":"Flying Saucer","b":"1F6F8","j":["UFO","transportation","vehicle","ufo"]},"bellhop-bell":{"a":"Bellhop Bell","b":"1F6CE","j":["bell","bellhop","hotel","service"]},"luggage":{"a":"Luggage","b":"1F9F3","j":["packing","travel"]},"hourglass-done":{"a":"Hourglass Done","b":"231B","j":["sand","timer","time","clock","oldschool","limit","exam","quiz","test"]},"hourglass-not-done":{"a":"Hourglass Not Done","b":"23F3","j":["hourglass","sand","timer","oldschool","time","countdown"]},"watch":{"a":"Watch","b":"231A","j":["clock","time","accessories"]},"alarm-clock":{"a":"Alarm Clock","b":"23F0","j":["alarm","clock","time","wake"]},"stopwatch":{"a":"Stopwatch","b":"23F1","j":["clock","time","deadline"]},"timer-clock":{"a":"Timer Clock","b":"23F2","j":["clock","timer","alarm"]},"mantelpiece-clock":{"a":"Mantelpiece Clock","b":"1F570","j":["clock","time"]},"twelve-oclock":{"a":"Twelve O’Clock","b":"1F55B","j":["00","12","12:00","clock","o’clock","twelve","twelve_o_clock","time","noon","midnight","midday","late","early","schedule"]},"twelvethirty":{"a":"Twelve-Thirty","b":"1F567","j":["12","12:30","clock","thirty","twelve","twelve-thirty","twelve_thirty","time","late","early","schedule"]},"one-oclock":{"a":"One O’Clock","b":"1F550","j":["00","1","1:00","clock","o’clock","one","one_o_clock","time","late","early","schedule"]},"onethirty":{"a":"One-Thirty","b":"1F55C","j":["1","1:30","clock","one","one-thirty","thirty","one_thirty","time","late","early","schedule"]},"two-oclock":{"a":"Two O’Clock","b":"1F551","j":["00","2","2:00","clock","o’clock","two","two_o_clock","time","late","early","schedule"]},"twothirty":{"a":"Two-Thirty","b":"1F55D","j":["2","2:30","clock","thirty","two","two-thirty","two_thirty","time","late","early","schedule"]},"three-oclock":{"a":"Three O’Clock","b":"1F552","j":["00","3","3:00","clock","o’clock","three","three_o_clock","time","late","early","schedule"]},"threethirty":{"a":"Three-Thirty","b":"1F55E","j":["3","3:30","clock","thirty","three","three-thirty","three_thirty","time","late","early","schedule"]},"four-oclock":{"a":"Four O’Clock","b":"1F553","j":["00","4","4:00","clock","four","o’clock","four_o_clock","time","late","early","schedule"]},"fourthirty":{"a":"Four-Thirty","b":"1F55F","j":["4","4:30","clock","four","four-thirty","thirty","four_thirty","time","late","early","schedule"]},"five-oclock":{"a":"Five O’Clock","b":"1F554","j":["00","5","5:00","clock","five","o’clock","five_o_clock","time","late","early","schedule"]},"fivethirty":{"a":"Five-Thirty","b":"1F560","j":["5","5:30","clock","five","five-thirty","thirty","five_thirty","time","late","early","schedule"]},"six-oclock":{"a":"Six O’Clock","b":"1F555","j":["00","6","6:00","clock","o’clock","six","six_o_clock","time","late","early","schedule","dawn","dusk"]},"sixthirty":{"a":"Six-Thirty","b":"1F561","j":["6","6:30","clock","six","six-thirty","thirty","six_thirty","time","late","early","schedule"]},"seven-oclock":{"a":"Seven O’Clock","b":"1F556","j":["00","7","7:00","clock","o’clock","seven","seven_o_clock","time","late","early","schedule"]},"seventhirty":{"a":"Seven-Thirty","b":"1F562","j":["7","7:30","clock","seven","seven-thirty","thirty","seven_thirty","time","late","early","schedule"]},"eight-oclock":{"a":"Eight O’Clock","b":"1F557","j":["00","8","8:00","clock","eight","o’clock","eight_o_clock","time","late","early","schedule"]},"eightthirty":{"a":"Eight-Thirty","b":"1F563","j":["8","8:30","clock","eight","eight-thirty","thirty","eight_thirty","time","late","early","schedule"]},"nine-oclock":{"a":"Nine O’Clock","b":"1F558","j":["00","9","9:00","clock","nine","o’clock","nine_o_clock","time","late","early","schedule"]},"ninethirty":{"a":"Nine-Thirty","b":"1F564","j":["9","9:30","clock","nine","nine-thirty","thirty","nine_thirty","time","late","early","schedule"]},"ten-oclock":{"a":"Ten O’Clock","b":"1F559","j":["00","10","10:00","clock","o’clock","ten","ten_o_clock","time","late","early","schedule"]},"tenthirty":{"a":"Ten-Thirty","b":"1F565","j":["10","10:30","clock","ten","ten-thirty","thirty","ten_thirty","time","late","early","schedule"]},"eleven-oclock":{"a":"Eleven O’Clock","b":"1F55A","j":["00","11","11:00","clock","eleven","o’clock","eleven_o_clock","time","late","early","schedule"]},"eleventhirty":{"a":"Eleven-Thirty","b":"1F566","j":["11","11:30","clock","eleven","eleven-thirty","thirty","eleven_thirty","time","late","early","schedule"]},"new-moon":{"a":"New Moon","b":"1F311","j":["dark","moon","nature","twilight","planet","space","night","evening","sleep"]},"waxing-crescent-moon":{"a":"Waxing Crescent Moon","b":"1F312","j":["crescent","moon","waxing","nature","twilight","planet","space","night","evening","sleep"]},"first-quarter-moon":{"a":"First Quarter Moon","b":"1F313","j":["moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"waxing-gibbous-moon":{"a":"Waxing Gibbous Moon","b":"1F314","j":["gibbous","moon","waxing","nature","night","sky","gray","twilight","planet","space","evening","sleep"]},"full-moon":{"a":"Full Moon","b":"1F315","j":["full","moon","nature","yellow","twilight","planet","space","night","evening","sleep"]},"waning-gibbous-moon":{"a":"Waning Gibbous Moon","b":"1F316","j":["gibbous","moon","waning","nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"]},"last-quarter-moon":{"a":"Last Quarter Moon","b":"1F317","j":["moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"waning-crescent-moon":{"a":"Waning Crescent Moon","b":"1F318","j":["crescent","moon","waning","nature","twilight","planet","space","night","evening","sleep"]},"crescent-moon":{"a":"Crescent Moon","b":"1F319","j":["crescent","moon","night","sleep","sky","evening","magic"]},"new-moon-face":{"a":"New Moon Face","b":"1F31A","j":["face","moon","nature","twilight","planet","space","night","evening","sleep"]},"first-quarter-moon-face":{"a":"First Quarter Moon Face","b":"1F31B","j":["face","moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"last-quarter-moon-face":{"a":"Last Quarter Moon Face","b":"1F31C","j":["face","moon","quarter","nature","twilight","planet","space","night","evening","sleep"]},"thermometer":{"a":"Thermometer","b":"1F321","j":["weather","temperature","hot","cold"]},"sun":{"a":"Sun","b":"2600","j":["bright","rays","sunny","weather","nature","brightness","summer","beach","spring"]},"full-moon-face":{"a":"Full Moon Face","b":"1F31D","j":["bright","face","full","moon","nature","twilight","planet","space","night","evening","sleep"]},"sun-with-face":{"a":"Sun with Face","b":"1F31E","j":["bright","face","sun","nature","morning","sky"]},"ringed-planet":{"a":"Ringed Planet","b":"1FA90","j":["saturn","saturnine","outerspace"]},"star":{"a":"Star","b":"2B50","j":["night","yellow"]},"glowing-star":{"a":"Glowing Star","b":"1F31F","j":["glittery","glow","shining","sparkle","star","night","awesome","good","magic"]},"shooting-star":{"a":"Shooting Star","b":"1F320","j":["falling","shooting","star","night","photo"]},"milky-way":{"a":"Milky Way","b":"1F30C","j":["space","photo","stars"]},"cloud":{"a":"Cloud","b":"2601","j":["weather","sky"]},"sun-behind-cloud":{"a":"Sun Behind Cloud","b":"26C5","j":["cloud","sun","weather","nature","cloudy","morning","fall","spring"]},"cloud-with-lightning-and-rain":{"a":"Cloud with Lightning and Rain","b":"26C8","j":["cloud","rain","thunder","weather","lightning"]},"sun-behind-small-cloud":{"a":"Sun Behind Small Cloud","b":"1F324","j":["cloud","sun","weather"]},"sun-behind-large-cloud":{"a":"Sun Behind Large Cloud","b":"1F325","j":["cloud","sun","weather"]},"sun-behind-rain-cloud":{"a":"Sun Behind Rain Cloud","b":"1F326","j":["cloud","rain","sun","weather"]},"cloud-with-rain":{"a":"Cloud with Rain","b":"1F327","j":["cloud","rain","weather"]},"cloud-with-snow":{"a":"Cloud with Snow","b":"1F328","j":["cloud","cold","snow","weather"]},"cloud-with-lightning":{"a":"Cloud with Lightning","b":"1F329","j":["cloud","lightning","weather","thunder"]},"tornado":{"a":"Tornado","b":"1F32A","j":["cloud","whirlwind","weather","cyclone","twister"]},"fog":{"a":"Fog","b":"1F32B","j":["cloud","weather"]},"wind-face":{"a":"Wind Face","b":"1F32C","j":["blow","cloud","face","wind","gust","air"]},"cyclone":{"a":"Cyclone","b":"1F300","j":["dizzy","hurricane","twister","typhoon","weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado"]},"rainbow":{"a":"Rainbow","b":"1F308","j":["rain","nature","happy","unicorn_face","photo","sky","spring"]},"closed-umbrella":{"a":"Closed Umbrella","b":"1F302","j":["clothing","rain","umbrella","weather","drizzle"]},"umbrella":{"a":"Umbrella","b":"2602","j":["clothing","rain","weather","spring"]},"umbrella-with-rain-drops":{"a":"Umbrella with Rain Drops","b":"2614","j":["clothing","drop","rain","umbrella","rainy","weather","spring"]},"umbrella-on-ground":{"a":"Umbrella on Ground","b":"26F1","j":["rain","sun","umbrella","weather","summer"]},"high-voltage":{"a":"High Voltage","b":"26A1","j":["danger","electric","lightning","voltage","zap","thunder","weather","lightning bolt","fast"]},"snowflake":{"a":"Snowflake","b":"2744","j":["cold","snow","winter","season","weather","christmas","xmas"]},"snowman":{"a":"Snowman","b":"2603","j":["cold","snow","winter","season","weather","christmas","xmas","frozen"]},"snowman-without-snow":{"a":"Snowman Without Snow","b":"26C4","j":["cold","snow","snowman","winter","season","weather","christmas","xmas","frozen","without_snow"]},"comet":{"a":"Comet","b":"2604","j":["space"]},"fire":{"a":"Fire","b":"1F525","j":["flame","tool","hot","cook"]},"droplet":{"a":"Droplet","b":"1F4A7","j":["cold","comic","drop","sweat","water","drip","faucet","spring"]},"water-wave":{"a":"Water Wave","b":"1F30A","j":["ocean","water","wave","sea","nature","tsunami","disaster"]},"jackolantern":{"a":"Jack-O-Lantern","b":"1F383","j":["celebration","halloween","jack","jack-o-lantern","lantern","jack_o_lantern","light","pumpkin","creepy","fall"]},"christmas-tree":{"a":"Christmas Tree","b":"1F384","j":["celebration","Christmas","tree","festival","vacation","december","xmas"]},"fireworks":{"a":"Fireworks","b":"1F386","j":["celebration","photo","festival","carnival","congratulations"]},"sparkler":{"a":"Sparkler","b":"1F387","j":["celebration","fireworks","sparkle","stars","night","shine"]},"firecracker":{"a":"Firecracker","b":"1F9E8","j":["dynamite","explosive","fireworks","boom","explode","explosion"]},"sparkles":{"a":"Sparkles","b":"2728","j":["*","sparkle","star","stars","shine","shiny","cool","awesome","good","magic"]},"balloon":{"a":"Balloon","b":"1F388","j":["celebration","party","birthday","circus"]},"party-popper":{"a":"Party Popper","b":"1F389","j":["celebration","party","popper","tada","congratulations","birthday","magic","circus"]},"confetti-ball":{"a":"Confetti Ball","b":"1F38A","j":["ball","celebration","confetti","festival","party","birthday","circus"]},"tanabata-tree":{"a":"Tanabata Tree","b":"1F38B","j":["banner","celebration","Japanese","tree","plant","nature","branch","summer"]},"pine-decoration":{"a":"Pine Decoration","b":"1F38D","j":["bamboo","celebration","Japanese","pine","plant","nature","vegetable","panda"]},"japanese-dolls":{"a":"Japanese Dolls","b":"1F38E","j":["celebration","doll","festival","Japanese","Japanese dolls","japanese","toy","kimono"]},"carp-streamer":{"a":"Carp Streamer","b":"1F38F","j":["carp","celebration","streamer","fish","japanese","koinobori","banner"]},"wind-chime":{"a":"Wind Chime","b":"1F390","j":["bell","celebration","chime","wind","nature","ding","spring"]},"moon-viewing-ceremony":{"a":"Moon Viewing Ceremony","b":"1F391","j":["celebration","ceremony","moon","photo","japan","asia","tsukimi"]},"red-envelope":{"a":"Red Envelope","b":"1F9E7","j":["gift","good luck","hóngbāo","lai see","money"]},"ribbon":{"a":"Ribbon","b":"1F380","j":["celebration","decoration","pink","girl","bowtie"]},"wrapped-gift":{"a":"Wrapped Gift","b":"1F381","j":["box","celebration","gift","present","wrapped","birthday","christmas","xmas"]},"reminder-ribbon":{"a":"Reminder Ribbon","b":"1F397","j":["celebration","reminder","ribbon","sports","cause","support","awareness"]},"admission-tickets":{"a":"Admission Tickets","b":"1F39F","j":["admission","ticket","sports","concert","entrance"]},"ticket":{"a":"Ticket","b":"1F3AB","j":["admission","event","concert","pass"]},"military-medal":{"a":"Military Medal","b":"1F396","j":["celebration","medal","military","award","winning","army"]},"trophy":{"a":"Trophy","b":"1F3C6","j":["prize","win","award","contest","place","ftw","ceremony"]},"sports-medal":{"a":"Sports Medal","b":"1F3C5","j":["medal","award","winning"]},"1st-place-medal":{"a":"1st Place Medal","b":"1F947","j":["first","gold","medal","award","winning"]},"2nd-place-medal":{"a":"2nd Place Medal","b":"1F948","j":["medal","second","silver","award"]},"3rd-place-medal":{"a":"3rd Place Medal","b":"1F949","j":["bronze","medal","third","award"]},"soccer-ball":{"a":"Soccer Ball","b":"26BD","j":["ball","football","soccer","sports"]},"baseball":{"a":"Baseball","b":"26BE","j":["ball","sports","balls"]},"softball":{"a":"Softball","b":"1F94E","j":["ball","glove","underarm","sports","balls"]},"basketball":{"a":"Basketball","b":"1F3C0","j":["ball","hoop","sports","balls","NBA"]},"volleyball":{"a":"Volleyball","b":"1F3D0","j":["ball","game","sports","balls"]},"american-football":{"a":"American Football","b":"1F3C8","j":["american","ball","football","sports","balls","NFL"]},"rugby-football":{"a":"Rugby Football","b":"1F3C9","j":["ball","football","rugby","sports","team"]},"tennis":{"a":"Tennis","b":"1F3BE","j":["ball","racquet","sports","balls","green"]},"flying-disc":{"a":"Flying Disc","b":"1F94F","j":["ultimate","sports","frisbee"]},"bowling":{"a":"Bowling","b":"1F3B3","j":["ball","game","sports","fun","play"]},"cricket-game":{"a":"Cricket Game","b":"1F3CF","j":["ball","bat","game","sports"]},"field-hockey":{"a":"Field Hockey","b":"1F3D1","j":["ball","field","game","hockey","stick","sports"]},"ice-hockey":{"a":"Ice Hockey","b":"1F3D2","j":["game","hockey","ice","puck","stick","sports"]},"lacrosse":{"a":"Lacrosse","b":"1F94D","j":["ball","goal","stick","sports"]},"ping-pong":{"a":"Ping Pong","b":"1F3D3","j":["ball","bat","game","paddle","table tennis","sports","pingpong"]},"badminton":{"a":"Badminton","b":"1F3F8","j":["birdie","game","racquet","shuttlecock","sports"]},"boxing-glove":{"a":"Boxing Glove","b":"1F94A","j":["boxing","glove","sports","fighting"]},"martial-arts-uniform":{"a":"Martial Arts Uniform","b":"1F94B","j":["judo","karate","martial arts","taekwondo","uniform"]},"goal-net":{"a":"Goal Net","b":"1F945","j":["goal","net","sports"]},"flag-in-hole":{"a":"Flag in Hole","b":"26F3","j":["golf","hole","sports","business","flag","summer"]},"ice-skate":{"a":"Ice Skate","b":"26F8","j":["ice","skate","sports"]},"fishing-pole":{"a":"Fishing Pole","b":"1F3A3","j":["fish","pole","food","hobby","summer"]},"diving-mask":{"a":"Diving Mask","b":"1F93F","j":["diving","scuba","snorkeling","sport","ocean"]},"running-shirt":{"a":"Running Shirt","b":"1F3BD","j":["athletics","running","sash","shirt","play","pageant"]},"skis":{"a":"Skis","b":"1F3BF","j":["ski","snow","sports","winter","cold"]},"sled":{"a":"Sled","b":"1F6F7","j":["sledge","sleigh","luge","toboggan"]},"curling-stone":{"a":"Curling Stone","b":"1F94C","j":["game","rock","sports"]},"bullseye":{"a":"Bullseye","b":"1F3AF","j":["dart","direct hit","game","hit","target","direct_hit","play","bar"]},"yoyo":{"a":"Yo-Yo","b":"1FA80","j":["fluctuate","toy","yo-yo","yo_yo"]},"kite":{"a":"Kite","b":"1FA81","j":["fly","soar","wind"]},"pool-8-ball":{"a":"Pool 8 Ball","b":"1F3B1","j":["8","ball","billiard","eight","game","pool","hobby","luck","magic"]},"crystal-ball":{"a":"Crystal Ball","b":"1F52E","j":["ball","crystal","fairy tale","fantasy","fortune","tool","disco","party","magic","circus","fortune_teller"]},"magic-wand":{"a":"Magic Wand","b":"1FA84","j":["magic","witch","wizard","supernature","power"]},"nazar-amulet":{"a":"Nazar Amulet","b":"1F9FF","j":["bead","charm","evil-eye","nazar","talisman"]},"video-game":{"a":"Video Game","b":"1F3AE","j":["controller","game","play","console","PS4"]},"joystick":{"a":"Joystick","b":"1F579","j":["game","video game","play"]},"slot-machine":{"a":"Slot Machine","b":"1F3B0","j":["game","slot","bet","gamble","vegas","fruit machine","luck","casino"]},"game-die":{"a":"Game Die","b":"1F3B2","j":["dice","die","game","random","tabletop","play","luck"]},"puzzle-piece":{"a":"Puzzle Piece","b":"1F9E9","j":["clue","interlocking","jigsaw","piece","puzzle"]},"teddy-bear":{"a":"Teddy Bear","b":"1F9F8","j":["plaything","plush","stuffed","toy"]},"piata":{"a":"Piñata","b":"1FA85","j":["celebration","party","piñata","pinata","mexico","candy"]},"nesting-dolls":{"a":"Nesting Dolls","b":"1FA86","j":["doll","nesting","russia","matryoshka","toy"]},"spade-suit":{"a":"Spade Suit","b":"2660","j":["card","game","poker","cards","suits","magic"]},"heart-suit":{"a":"Heart Suit","b":"2665","j":["card","game","poker","cards","magic","suits"]},"diamond-suit":{"a":"Diamond Suit","b":"2666","j":["card","game","poker","cards","magic","suits"]},"club-suit":{"a":"Club Suit","b":"2663","j":["card","game","poker","cards","magic","suits"]},"chess-pawn":{"a":"Chess Pawn","b":"265F","j":["chess","dupe","expendable"]},"joker":{"a":"Joker","b":"1F0CF","j":["card","game","wildcard","poker","cards","play","magic"]},"mahjong-red-dragon":{"a":"Mahjong Red Dragon","b":"1F004","j":["game","mahjong","red","play","chinese","kanji"]},"flower-playing-cards":{"a":"Flower Playing Cards","b":"1F3B4","j":["card","flower","game","Japanese","playing","sunset","red"]},"performing-arts":{"a":"Performing Arts","b":"1F3AD","j":["art","mask","performing","theater","theatre","acting","drama"]},"framed-picture":{"a":"Framed Picture","b":"1F5BC","j":["art","frame","museum","painting","picture","photography"]},"artist-palette":{"a":"Artist Palette","b":"1F3A8","j":["art","museum","painting","palette","design","paint","draw","colors"]},"thread":{"a":"Thread","b":"1F9F5","j":["needle","sewing","spool","string"]},"sewing-needle":{"a":"Sewing Needle","b":"1FAA1","j":["embroidery","needle","sewing","stitches","sutures","tailoring"]},"yarn":{"a":"Yarn","b":"1F9F6","j":["ball","crochet","knit"]},"knot":{"a":"Knot","b":"1FAA2","j":["rope","tangled","tie","twine","twist","scout"]},"glasses":{"a":"Glasses","b":"1F453","j":["clothing","eye","eyeglasses","eyewear","fashion","accessories","eyesight","nerdy","dork","geek"]},"sunglasses":{"a":"Sunglasses","b":"1F576","j":["dark","eye","eyewear","glasses","face","cool","accessories"]},"goggles":{"a":"Goggles","b":"1F97D","j":["eye protection","swimming","welding","eyes","protection","safety"]},"lab-coat":{"a":"Lab Coat","b":"1F97C","j":["doctor","experiment","scientist","chemist"]},"safety-vest":{"a":"Safety Vest","b":"1F9BA","j":["emergency","safety","vest","protection"]},"necktie":{"a":"Necktie","b":"1F454","j":["clothing","tie","shirt","suitup","formal","fashion","cloth","business"]},"tshirt":{"a":"T-Shirt","b":"1F455","j":["clothing","shirt","t-shirt","t_shirt","fashion","cloth","casual","tee"]},"jeans":{"a":"Jeans","b":"1F456","j":["clothing","pants","trousers","fashion","shopping"]},"scarf":{"a":"Scarf","b":"1F9E3","j":["neck","winter","clothes"]},"gloves":{"a":"Gloves","b":"1F9E4","j":["hand","hands","winter","clothes"]},"coat":{"a":"Coat","b":"1F9E5","j":["jacket"]},"socks":{"a":"Socks","b":"1F9E6","j":["stocking","stockings","clothes"]},"dress":{"a":"Dress","b":"1F457","j":["clothing","clothes","fashion","shopping"]},"kimono":{"a":"Kimono","b":"1F458","j":["clothing","dress","fashion","women","female","japanese"]},"sari":{"a":"Sari","b":"1F97B","j":["clothing","dress"]},"onepiece-swimsuit":{"a":"One-Piece Swimsuit","b":"1FA71","j":["bathing suit","one-piece swimsuit","one_piece_swimsuit","fashion"]},"briefs":{"a":"Briefs","b":"1FA72","j":["bathing suit","one-piece","swimsuit","underwear","clothing"]},"shorts":{"a":"Shorts","b":"1FA73","j":["bathing suit","pants","underwear","clothing"]},"bikini":{"a":"Bikini","b":"1F459","j":["clothing","swim","swimming","female","woman","girl","fashion","beach","summer"]},"womans-clothes":{"a":"Woman’S Clothes","b":"1F45A","j":["clothing","woman","woman’s clothes","woman_s_clothes","fashion","shopping_bags","female"]},"purse":{"a":"Purse","b":"1F45B","j":["clothing","coin","fashion","accessories","money","sales","shopping"]},"handbag":{"a":"Handbag","b":"1F45C","j":["bag","clothing","purse","fashion","accessory","accessories","shopping"]},"clutch-bag":{"a":"Clutch Bag","b":"1F45D","j":["bag","clothing","pouch","accessories","shopping"]},"shopping-bags":{"a":"Shopping Bags","b":"1F6CD","j":["bag","hotel","shopping","mall","buy","purchase"]},"backpack":{"a":"Backpack","b":"1F392","j":["bag","rucksack","satchel","school","student","education"]},"thong-sandal":{"a":"Thong Sandal","b":"1FA74","j":["beach sandals","sandals","thong sandals","thongs","zōri","footwear","summer"]},"mans-shoe":{"a":"Man’S Shoe","b":"1F45E","j":["clothing","man","man’s shoe","shoe","man_s_shoe","fashion","male"]},"running-shoe":{"a":"Running Shoe","b":"1F45F","j":["athletic","clothing","shoe","sneaker","shoes","sports","sneakers"]},"hiking-boot":{"a":"Hiking Boot","b":"1F97E","j":["backpacking","boot","camping","hiking"]},"flat-shoe":{"a":"Flat Shoe","b":"1F97F","j":["ballet flat","slip-on","slipper","ballet"]},"highheeled-shoe":{"a":"High-Heeled Shoe","b":"1F460","j":["clothing","heel","high-heeled shoe","shoe","woman","high_heeled_shoe","fashion","shoes","female","pumps","stiletto"]},"womans-sandal":{"a":"Woman’S Sandal","b":"1F461","j":["clothing","sandal","shoe","woman","woman’s sandal","woman_s_sandal","shoes","fashion","flip flops"]},"ballet-shoes":{"a":"Ballet Shoes","b":"1FA70","j":["ballet","dance"]},"womans-boot":{"a":"Woman’S Boot","b":"1F462","j":["boot","clothing","shoe","woman","woman’s boot","woman_s_boot","shoes","fashion"]},"crown":{"a":"Crown","b":"1F451","j":["clothing","king","queen","kod","leader","royalty","lord"]},"womans-hat":{"a":"Woman’S Hat","b":"1F452","j":["clothing","hat","woman","woman’s hat","woman_s_hat","fashion","accessories","female","lady","spring"]},"top-hat":{"a":"Top Hat","b":"1F3A9","j":["clothing","hat","top","tophat","magic","gentleman","classy","circus"]},"graduation-cap":{"a":"Graduation Cap","b":"1F393","j":["cap","celebration","clothing","graduation","hat","school","college","degree","university","legal","learn","education"]},"billed-cap":{"a":"Billed Cap","b":"1F9E2","j":["baseball cap","cap","baseball"]},"military-helmet":{"a":"Military Helmet","b":"1FA96","j":["army","helmet","military","soldier","warrior","protection"]},"rescue-workers-helmet":{"a":"Rescue Worker’S Helmet","b":"26D1","j":["aid","cross","face","hat","helmet","rescue worker’s helmet","rescue_worker_s_helmet","construction","build"]},"prayer-beads":{"a":"Prayer Beads","b":"1F4FF","j":["beads","clothing","necklace","prayer","religion","dhikr","religious"]},"lipstick":{"a":"Lipstick","b":"1F484","j":["cosmetics","makeup","female","girl","fashion","woman"]},"ring":{"a":"Ring","b":"1F48D","j":["diamond","wedding","propose","marriage","valentines","fashion","jewelry","gem","engagement"]},"gem-stone":{"a":"Gem Stone","b":"1F48E","j":["diamond","gem","jewel","blue","ruby","jewelry"]},"muted-speaker":{"a":"Muted Speaker","b":"1F507","j":["mute","quiet","silent","speaker","sound","volume","silence"]},"speaker-low-volume":{"a":"Speaker Low Volume","b":"1F508","j":["soft","sound","volume","silence","broadcast"]},"speaker-medium-volume":{"a":"Speaker Medium Volume","b":"1F509","j":["medium","volume","speaker","broadcast"]},"speaker-high-volume":{"a":"Speaker High Volume","b":"1F50A","j":["loud","volume","noise","noisy","speaker","broadcast"]},"loudspeaker":{"a":"Loudspeaker","b":"1F4E2","j":["loud","public address","volume","sound"]},"megaphone":{"a":"Megaphone","b":"1F4E3","j":["cheering","sound","speaker","volume"]},"postal-horn":{"a":"Postal Horn","b":"1F4EF","j":["horn","post","postal","instrument","music"]},"bell":{"a":"Bell","b":"1F514","j":["sound","notification","christmas","xmas","chime"]},"bell-with-slash":{"a":"Bell with Slash","b":"1F515","j":["bell","forbidden","mute","quiet","silent","sound","volume"]},"musical-score":{"a":"Musical Score","b":"1F3BC","j":["music","score","treble","clef","compose"]},"musical-note":{"a":"Musical Note","b":"1F3B5","j":["music","note","score","tone","sound"]},"musical-notes":{"a":"Musical Notes","b":"1F3B6","j":["music","note","notes","score"]},"studio-microphone":{"a":"Studio Microphone","b":"1F399","j":["mic","microphone","music","studio","sing","recording","artist","talkshow"]},"level-slider":{"a":"Level Slider","b":"1F39A","j":["level","music","slider","scale"]},"control-knobs":{"a":"Control Knobs","b":"1F39B","j":["control","knobs","music","dial"]},"microphone":{"a":"Microphone","b":"1F3A4","j":["karaoke","mic","sound","music","PA","sing","talkshow"]},"headphone":{"a":"Headphone","b":"1F3A7","j":["earbud","music","score","gadgets"]},"radio":{"a":"Radio","b":"1F4FB","j":["video","communication","music","podcast","program"]},"saxophone":{"a":"Saxophone","b":"1F3B7","j":["instrument","music","sax","jazz","blues"]},"accordion":{"a":"Accordion","b":"1FA97","j":["concertina","squeeze box","music"]},"guitar":{"a":"Guitar","b":"1F3B8","j":["instrument","music"]},"musical-keyboard":{"a":"Musical Keyboard","b":"1F3B9","j":["instrument","keyboard","music","piano","compose"]},"trumpet":{"a":"Trumpet","b":"1F3BA","j":["instrument","music","brass"]},"violin":{"a":"Violin","b":"1F3BB","j":["instrument","music","orchestra","symphony"]},"banjo":{"a":"Banjo","b":"1FA95","j":["music","stringed","instructment"]},"drum":{"a":"Drum","b":"1F941","j":["drumsticks","music","instrument","snare"]},"long-drum":{"a":"Long Drum","b":"1FA98","j":["beat","conga","drum","rhythm","music"]},"mobile-phone":{"a":"Mobile Phone","b":"1F4F1","j":["cell","mobile","phone","telephone","technology","apple","gadgets","dial"]},"mobile-phone-with-arrow":{"a":"Mobile Phone with Arrow","b":"1F4F2","j":["arrow","cell","mobile","phone","receive","iphone","incoming"]},"telephone":{"a":"Telephone","b":"260E","j":["phone","technology","communication","dial"]},"telephone-receiver":{"a":"Telephone Receiver","b":"1F4DE","j":["phone","receiver","telephone","technology","communication","dial"]},"pager":{"a":"Pager","b":"1F4DF","j":["bbcall","oldschool","90s"]},"fax-machine":{"a":"Fax Machine","b":"1F4E0","j":["fax","communication","technology"]},"battery":{"a":"Battery","b":"1F50B","j":["power","energy","sustain"]},"electric-plug":{"a":"Electric Plug","b":"1F50C","j":["electric","electricity","plug","charger","power"]},"laptop":{"a":"Laptop","b":"1F4BB","j":["computer","pc","personal","technology","screen","display","monitor"]},"desktop-computer":{"a":"Desktop Computer","b":"1F5A5","j":["computer","desktop","technology","computing","screen"]},"printer":{"a":"Printer","b":"1F5A8","j":["computer","paper","ink"]},"keyboard":{"a":"Keyboard","b":"2328","j":["computer","technology","type","input","text"]},"computer-mouse":{"a":"Computer Mouse","b":"1F5B1","j":["computer","click"]},"trackball":{"a":"Trackball","b":"1F5B2","j":["computer","technology","trackpad"]},"computer-disk":{"a":"Computer Disk","b":"1F4BD","j":["computer","disk","minidisk","optical","technology","record","data","90s"]},"floppy-disk":{"a":"Floppy Disk","b":"1F4BE","j":["computer","disk","floppy","oldschool","technology","save","90s","80s"]},"optical-disk":{"a":"Optical Disk","b":"1F4BF","j":["cd","computer","disk","optical","technology","dvd","disc","90s"]},"dvd":{"a":"Dvd","b":"1F4C0","j":["blu-ray","computer","disk","optical","cd","disc"]},"abacus":{"a":"Abacus","b":"1F9EE","j":["calculation"]},"movie-camera":{"a":"Movie Camera","b":"1F3A5","j":["camera","cinema","movie","film","record"]},"film-frames":{"a":"Film Frames","b":"1F39E","j":["cinema","film","frames","movie"]},"film-projector":{"a":"Film Projector","b":"1F4FD","j":["cinema","film","movie","projector","video","tape","record"]},"clapper-board":{"a":"Clapper Board","b":"1F3AC","j":["clapper","movie","film","record"]},"television":{"a":"Television","b":"1F4FA","j":["tv","video","technology","program","oldschool","show"]},"camera":{"a":"Camera","b":"1F4F7","j":["video","gadgets","photography"]},"camera-with-flash":{"a":"Camera with Flash","b":"1F4F8","j":["camera","flash","video","photography","gadgets"]},"video-camera":{"a":"Video Camera","b":"1F4F9","j":["camera","video","film","record"]},"videocassette":{"a":"Videocassette","b":"1F4FC","j":["tape","vhs","video","record","oldschool","90s","80s"]},"magnifying-glass-tilted-left":{"a":"Magnifying Glass Tilted Left","b":"1F50D","j":["glass","magnifying","search","tool","zoom","find","detective"]},"magnifying-glass-tilted-right":{"a":"Magnifying Glass Tilted Right","b":"1F50E","j":["glass","magnifying","search","tool","zoom","find","detective"]},"candle":{"a":"Candle","b":"1F56F","j":["light","fire","wax"]},"light-bulb":{"a":"Light Bulb","b":"1F4A1","j":["bulb","comic","electric","idea","light","electricity"]},"flashlight":{"a":"Flashlight","b":"1F526","j":["electric","light","tool","torch","dark","camping","sight","night"]},"red-paper-lantern":{"a":"Red Paper Lantern","b":"1F3EE","j":["bar","lantern","light","red","paper","halloween","spooky"]},"diya-lamp":{"a":"Diya Lamp","b":"1FA94","j":["diya","lamp","oil","lighting"]},"notebook-with-decorative-cover":{"a":"Notebook with Decorative Cover","b":"1F4D4","j":["book","cover","decorated","notebook","classroom","notes","record","paper","study"]},"closed-book":{"a":"Closed Book","b":"1F4D5","j":["book","closed","read","library","knowledge","textbook","learn"]},"open-book":{"a":"Open Book","b":"1F4D6","j":["book","open","read","library","knowledge","literature","learn","study"]},"green-book":{"a":"Green Book","b":"1F4D7","j":["book","green","read","library","knowledge","study"]},"blue-book":{"a":"Blue Book","b":"1F4D8","j":["blue","book","read","library","knowledge","learn","study"]},"orange-book":{"a":"Orange Book","b":"1F4D9","j":["book","orange","read","library","knowledge","textbook","study"]},"books":{"a":"Books","b":"1F4DA","j":["book","literature","library","study"]},"notebook":{"a":"Notebook","b":"1F4D3","j":["stationery","record","notes","paper","study"]},"ledger":{"a":"Ledger","b":"1F4D2","j":["notebook","notes","paper"]},"page-with-curl":{"a":"Page with Curl","b":"1F4C3","j":["curl","document","page","documents","office","paper"]},"scroll":{"a":"Scroll","b":"1F4DC","j":["paper","documents","ancient","history"]},"page-facing-up":{"a":"Page Facing Up","b":"1F4C4","j":["document","page","documents","office","paper","information"]},"newspaper":{"a":"Newspaper","b":"1F4F0","j":["news","paper","press","headline"]},"rolledup-newspaper":{"a":"Rolled-Up Newspaper","b":"1F5DE","j":["news","newspaper","paper","rolled","rolled-up newspaper","rolled_up_newspaper","press","headline"]},"bookmark-tabs":{"a":"Bookmark Tabs","b":"1F4D1","j":["bookmark","mark","marker","tabs","favorite","save","order","tidy"]},"bookmark":{"a":"Bookmark","b":"1F516","j":["mark","favorite","label","save"]},"label":{"a":"Label","b":"1F3F7","j":["sale","tag"]},"money-bag":{"a":"Money Bag","b":"1F4B0","j":["bag","dollar","money","moneybag","payment","coins","sale"]},"coin":{"a":"Coin","b":"1FA99","j":["gold","metal","money","silver","treasure","currency"]},"yen-banknote":{"a":"Yen Banknote","b":"1F4B4","j":["banknote","bill","currency","money","note","yen","sales","japanese","dollar"]},"dollar-banknote":{"a":"Dollar Banknote","b":"1F4B5","j":["banknote","bill","currency","dollar","money","note","sales"]},"euro-banknote":{"a":"Euro Banknote","b":"1F4B6","j":["banknote","bill","currency","euro","money","note","sales","dollar"]},"pound-banknote":{"a":"Pound Banknote","b":"1F4B7","j":["banknote","bill","currency","money","note","pound","british","sterling","sales","bills","uk","england"]},"money-with-wings":{"a":"Money with Wings","b":"1F4B8","j":["banknote","bill","fly","money","wings","dollar","bills","payment","sale"]},"credit-card":{"a":"Credit Card","b":"1F4B3","j":["card","credit","money","sales","dollar","bill","payment","shopping"]},"receipt":{"a":"Receipt","b":"1F9FE","j":["accounting","bookkeeping","evidence","proof","expenses"]},"chart-increasing-with-yen":{"a":"Chart Increasing with Yen","b":"1F4B9","j":["chart","graph","growth","money","yen","green-square","presentation","stats"]},"envelope":{"a":"Envelope","b":"2709","j":["email","letter","postal","inbox","communication"]},"email":{"a":"E-Mail","b":"1F4E7","j":["e-mail","letter","mail","e_mail","communication","inbox"]},"incoming-envelope":{"a":"Incoming Envelope","b":"1F4E8","j":["e-mail","email","envelope","incoming","letter","receive","inbox"]},"envelope-with-arrow":{"a":"Envelope with Arrow","b":"1F4E9","j":["arrow","e-mail","email","envelope","outgoing","communication"]},"outbox-tray":{"a":"Outbox Tray","b":"1F4E4","j":["box","letter","mail","outbox","sent","tray","inbox","email"]},"inbox-tray":{"a":"Inbox Tray","b":"1F4E5","j":["box","inbox","letter","mail","receive","tray","email","documents"]},"package":{"a":"Package","b":"1F4E6","j":["box","parcel","mail","gift","cardboard","moving"]},"closed-mailbox-with-raised-flag":{"a":"Closed Mailbox with Raised Flag","b":"1F4EB","j":["closed","mail","mailbox","postbox","email","inbox","communication"]},"closed-mailbox-with-lowered-flag":{"a":"Closed Mailbox with Lowered Flag","b":"1F4EA","j":["closed","lowered","mail","mailbox","postbox","email","communication","inbox"]},"open-mailbox-with-raised-flag":{"a":"Open Mailbox with Raised Flag","b":"1F4EC","j":["mail","mailbox","open","postbox","email","inbox","communication"]},"open-mailbox-with-lowered-flag":{"a":"Open Mailbox with Lowered Flag","b":"1F4ED","j":["lowered","mail","mailbox","open","postbox","email","inbox"]},"postbox":{"a":"Postbox","b":"1F4EE","j":["mail","mailbox","email","letter","envelope"]},"ballot-box-with-ballot":{"a":"Ballot Box with Ballot","b":"1F5F3","j":["ballot","box","election","vote"]},"pencil":{"a":"Pencil","b":"270F","j":["stationery","write","paper","writing","school","study"]},"black-nib":{"a":"Black Nib","b":"2712","j":["nib","pen","stationery","writing","write"]},"fountain-pen":{"a":"Fountain Pen","b":"1F58B","j":["fountain","pen","stationery","writing","write"]},"pen":{"a":"Pen","b":"1F58A","j":["ballpoint","stationery","writing","write"]},"paintbrush":{"a":"Paintbrush","b":"1F58C","j":["painting","drawing","creativity","art"]},"crayon":{"a":"Crayon","b":"1F58D","j":["drawing","creativity"]},"memo":{"a":"Memo","b":"1F4DD","j":["pencil","write","documents","stationery","paper","writing","legal","exam","quiz","test","study","compose"]},"briefcase":{"a":"Briefcase","b":"1F4BC","j":["business","documents","work","law","legal","job","career"]},"file-folder":{"a":"File Folder","b":"1F4C1","j":["file","folder","documents","business","office"]},"open-file-folder":{"a":"Open File Folder","b":"1F4C2","j":["file","folder","open","documents","load"]},"card-index-dividers":{"a":"Card Index Dividers","b":"1F5C2","j":["card","dividers","index","organizing","business","stationery"]},"calendar":{"a":"Calendar","b":"1F4C5","j":["date","schedule"]},"tearoff-calendar":{"a":"Tear-off Calendar","b":"1F4C6","j":["calendar","tear-off calendar","tear_off_calendar","schedule","date","planning"]},"spiral-notepad":{"a":"Spiral Notepad","b":"1F5D2","j":["note","pad","spiral","memo","stationery"]},"spiral-calendar":{"a":"Spiral Calendar","b":"1F5D3","j":["calendar","pad","spiral","date","schedule","planning"]},"card-index":{"a":"Card Index","b":"1F4C7","j":["card","index","rolodex","business","stationery"]},"chart-increasing":{"a":"Chart Increasing","b":"1F4C8","j":["chart","graph","growth","trend","upward","presentation","stats","recovery","business","economics","money","sales","good","success"]},"chart-decreasing":{"a":"Chart Decreasing","b":"1F4C9","j":["chart","down","graph","trend","presentation","stats","recession","business","economics","money","sales","bad","failure"]},"bar-chart":{"a":"Bar Chart","b":"1F4CA","j":["bar","chart","graph","presentation","stats"]},"clipboard":{"a":"Clipboard","b":"1F4CB","j":["stationery","documents"]},"pushpin":{"a":"Pushpin","b":"1F4CC","j":["pin","stationery","mark","here"]},"round-pushpin":{"a":"Round Pushpin","b":"1F4CD","j":["pin","pushpin","stationery","location","map","here"]},"paperclip":{"a":"Paperclip","b":"1F4CE","j":["documents","stationery"]},"linked-paperclips":{"a":"Linked Paperclips","b":"1F587","j":["link","paperclip","documents","stationery"]},"straight-ruler":{"a":"Straight Ruler","b":"1F4CF","j":["ruler","straight edge","stationery","calculate","length","math","school","drawing","architect","sketch"]},"triangular-ruler":{"a":"Triangular Ruler","b":"1F4D0","j":["ruler","set","triangle","stationery","math","architect","sketch"]},"scissors":{"a":"Scissors","b":"2702","j":["cutting","tool","stationery","cut"]},"card-file-box":{"a":"Card File Box","b":"1F5C3","j":["box","card","file","business","stationery"]},"file-cabinet":{"a":"File Cabinet","b":"1F5C4","j":["cabinet","file","filing","organizing"]},"wastebasket":{"a":"Wastebasket","b":"1F5D1","j":["bin","trash","rubbish","garbage","toss"]},"locked":{"a":"Locked","b":"1F512","j":["closed","security","password","padlock"]},"unlocked":{"a":"Unlocked","b":"1F513","j":["lock","open","unlock","privacy","security"]},"locked-with-pen":{"a":"Locked with Pen","b":"1F50F","j":["ink","lock","nib","pen","privacy","security","secret"]},"locked-with-key":{"a":"Locked with Key","b":"1F510","j":["closed","key","lock","secure","security","privacy"]},"key":{"a":"Key","b":"1F511","j":["lock","password","door"]},"old-key":{"a":"Old Key","b":"1F5DD","j":["clue","key","lock","old","door","password"]},"hammer":{"a":"Hammer","b":"1F528","j":["tool","tools","build","create"]},"axe":{"a":"Axe","b":"1FA93","j":["chop","hatchet","split","wood","tool","cut"]},"pick":{"a":"Pick","b":"26CF","j":["mining","tool","tools","dig"]},"hammer-and-pick":{"a":"Hammer and Pick","b":"2692","j":["hammer","pick","tool","tools","build","create"]},"hammer-and-wrench":{"a":"Hammer and Wrench","b":"1F6E0","j":["hammer","spanner","tool","wrench","tools","build","create"]},"dagger":{"a":"Dagger","b":"1F5E1","j":["knife","weapon"]},"crossed-swords":{"a":"Crossed Swords","b":"2694","j":["crossed","swords","weapon"]},"water-pistol":{"a":"Water Pistol","b":"1F52B","j":["gun","handgun","pistol","revolver","tool","water","weapon","violence"]},"boomerang":{"a":"Boomerang","b":"1FA83","j":["australia","rebound","repercussion","weapon"]},"bow-and-arrow":{"a":"Bow and Arrow","b":"1F3F9","j":["archer","arrow","bow","Sagittarius","zodiac","sports"]},"shield":{"a":"Shield","b":"1F6E1","j":["weapon","protection","security"]},"carpentry-saw":{"a":"Carpentry Saw","b":"1FA9A","j":["carpenter","lumber","saw","tool","cut","chop"]},"wrench":{"a":"Wrench","b":"1F527","j":["spanner","tool","tools","diy","ikea","fix","maintainer"]},"screwdriver":{"a":"Screwdriver","b":"1FA9B","j":["screw","tool","tools"]},"nut-and-bolt":{"a":"Nut and Bolt","b":"1F529","j":["bolt","nut","tool","handy","tools","fix"]},"gear":{"a":"Gear","b":"2699","j":["cog","cogwheel","tool"]},"clamp":{"a":"Clamp","b":"1F5DC","j":["compress","tool","vice"]},"balance-scale":{"a":"Balance Scale","b":"2696","j":["balance","justice","Libra","scale","zodiac","law","fairness","weight"]},"white-cane":{"a":"White Cane","b":"1F9AF","j":["accessibility","blind","probing_cane"]},"link":{"a":"Link","b":"1F517","j":["rings","url"]},"chains":{"a":"Chains","b":"26D3","j":["chain","lock","arrest"]},"hook":{"a":"Hook","b":"1FA9D","j":["catch","crook","curve","ensnare","selling point","tools"]},"toolbox":{"a":"Toolbox","b":"1F9F0","j":["chest","mechanic","tool","tools","diy","fix","maintainer"]},"magnet":{"a":"Magnet","b":"1F9F2","j":["attraction","horseshoe","magnetic"]},"ladder":{"a":"Ladder","b":"1FA9C","j":["climb","rung","step","tools"]},"alembic":{"a":"Alembic","b":"2697","j":["chemistry","tool","distilling","science","experiment"]},"test-tube":{"a":"Test Tube","b":"1F9EA","j":["chemist","chemistry","experiment","lab","science"]},"petri-dish":{"a":"Petri Dish","b":"1F9EB","j":["bacteria","biologist","biology","culture","lab"]},"dna":{"a":"Dna","b":"1F9EC","j":["biologist","evolution","gene","genetics","life"]},"microscope":{"a":"Microscope","b":"1F52C","j":["science","tool","laboratory","experiment","zoomin","study"]},"telescope":{"a":"Telescope","b":"1F52D","j":["science","tool","stars","space","zoom","astronomy"]},"satellite-antenna":{"a":"Satellite Antenna","b":"1F4E1","j":["antenna","dish","satellite","communication","future","radio","space"]},"syringe":{"a":"Syringe","b":"1F489","j":["medicine","needle","shot","sick","health","hospital","drugs","blood","doctor","nurse"]},"drop-of-blood":{"a":"Drop of Blood","b":"1FA78","j":["bleed","blood donation","injury","medicine","menstruation","period","hurt","harm","wound"]},"pill":{"a":"Pill","b":"1F48A","j":["doctor","medicine","sick","health","pharmacy","drug"]},"adhesive-bandage":{"a":"Adhesive Bandage","b":"1FA79","j":["bandage","heal"]},"stethoscope":{"a":"Stethoscope","b":"1FA7A","j":["doctor","heart","medicine","health"]},"door":{"a":"Door","b":"1F6AA","j":["house","entry","exit"]},"elevator":{"a":"Elevator","b":"1F6D7","j":["accessibility","hoist","lift"]},"mirror":{"a":"Mirror","b":"1FA9E","j":["reflection","reflector","speculum"]},"window":{"a":"Window","b":"1FA9F","j":["frame","fresh air","opening","transparent","view","scenery"]},"bed":{"a":"Bed","b":"1F6CF","j":["hotel","sleep","rest"]},"couch-and-lamp":{"a":"Couch and Lamp","b":"1F6CB","j":["couch","hotel","lamp","read","chill"]},"chair":{"a":"Chair","b":"1FA91","j":["seat","sit","furniture"]},"toilet":{"a":"Toilet","b":"1F6BD","j":["restroom","wc","washroom","bathroom","potty"]},"plunger":{"a":"Plunger","b":"1FAA0","j":["force cup","plumber","suction","toilet"]},"shower":{"a":"Shower","b":"1F6BF","j":["water","clean","bathroom"]},"bathtub":{"a":"Bathtub","b":"1F6C1","j":["bath","clean","shower","bathroom"]},"mouse-trap":{"a":"Mouse Trap","b":"1FAA4","j":["bait","mousetrap","snare","trap","cheese"]},"razor":{"a":"Razor","b":"1FA92","j":["sharp","shave","cut"]},"lotion-bottle":{"a":"Lotion Bottle","b":"1F9F4","j":["lotion","moisturizer","shampoo","sunscreen"]},"safety-pin":{"a":"Safety Pin","b":"1F9F7","j":["diaper","punk rock"]},"broom":{"a":"Broom","b":"1F9F9","j":["cleaning","sweeping","witch"]},"basket":{"a":"Basket","b":"1F9FA","j":["farming","laundry","picnic"]},"roll-of-paper":{"a":"Roll of Paper","b":"1F9FB","j":["paper towels","toilet paper","roll"]},"bucket":{"a":"Bucket","b":"1FAA3","j":["cask","pail","vat","water","container"]},"soap":{"a":"Soap","b":"1F9FC","j":["bar","bathing","cleaning","lather","soapdish"]},"toothbrush":{"a":"Toothbrush","b":"1FAA5","j":["bathroom","brush","clean","dental","hygiene","teeth"]},"sponge":{"a":"Sponge","b":"1F9FD","j":["absorbing","cleaning","porous"]},"fire-extinguisher":{"a":"Fire Extinguisher","b":"1F9EF","j":["extinguish","fire","quench"]},"shopping-cart":{"a":"Shopping Cart","b":"1F6D2","j":["cart","shopping","trolley"]},"cigarette":{"a":"Cigarette","b":"1F6AC","j":["smoking","kills","tobacco","joint","smoke"]},"coffin":{"a":"Coffin","b":"26B0","j":["death","vampire","dead","die","rip","graveyard","cemetery","casket","funeral","box"]},"headstone":{"a":"Headstone","b":"1FAA6","j":["cemetery","grave","graveyard","tombstone","death","rip"]},"funeral-urn":{"a":"Funeral Urn","b":"26B1","j":["ashes","death","funeral","urn","dead","die","rip"]},"moai":{"a":"Moai","b":"1F5FF","j":["face","moyai","statue","rock","easter island"]},"placard":{"a":"Placard","b":"1FAA7","j":["demonstration","picket","protest","sign","announcement"]},"atm-sign":{"a":"Atm Sign","b":"1F3E7","j":["atm","ATM sign","automated","bank","teller","money","sales","cash","blue-square","payment"]},"litter-in-bin-sign":{"a":"Litter in Bin Sign","b":"1F6AE","j":["litter","litter bin","blue-square","sign","human","info"]},"potable-water":{"a":"Potable Water","b":"1F6B0","j":["drinking","potable","water","blue-square","liquid","restroom","cleaning","faucet"]},"wheelchair-symbol":{"a":"Wheelchair Symbol","b":"267F","j":["access","blue-square","disabled","accessibility"]},"mens-room":{"a":"Men’S Room","b":"1F6B9","j":["lavatory","man","men’s room","restroom","wc","men_s_room","toilet","blue-square","gender","male"]},"womens-room":{"a":"Women’S Room","b":"1F6BA","j":["lavatory","restroom","wc","woman","women’s room","women_s_room","purple-square","female","toilet","loo","gender"]},"restroom":{"a":"Restroom","b":"1F6BB","j":["lavatory","WC","blue-square","toilet","refresh","wc","gender"]},"baby-symbol":{"a":"Baby Symbol","b":"1F6BC","j":["baby","changing","orange-square","child"]},"water-closet":{"a":"Water Closet","b":"1F6BE","j":["closet","lavatory","restroom","water","wc","toilet","blue-square"]},"passport-control":{"a":"Passport Control","b":"1F6C2","j":["control","passport","custom","blue-square"]},"customs":{"a":"Customs","b":"1F6C3","j":["passport","border","blue-square"]},"baggage-claim":{"a":"Baggage Claim","b":"1F6C4","j":["baggage","claim","blue-square","airport","transport"]},"left-luggage":{"a":"Left Luggage","b":"1F6C5","j":["baggage","locker","luggage","blue-square","travel"]},"warning":{"a":"Warning","b":"26A0","j":["exclamation","wip","alert","error","problem","issue"]},"children-crossing":{"a":"Children Crossing","b":"1F6B8","j":["child","crossing","pedestrian","traffic","school","warning","danger","sign","driving","yellow-diamond"]},"no-entry":{"a":"No Entry","b":"26D4","j":["entry","forbidden","no","not","prohibited","traffic","limit","security","privacy","bad","denied","stop","circle"]},"prohibited":{"a":"Prohibited","b":"1F6AB","j":["entry","forbidden","no","not","forbid","stop","limit","denied","disallow","circle"]},"no-bicycles":{"a":"No Bicycles","b":"1F6B3","j":["bicycle","bike","forbidden","no","prohibited","cyclist","circle"]},"no-smoking":{"a":"No Smoking","b":"1F6AD","j":["forbidden","no","not","prohibited","smoking","cigarette","blue-square","smell","smoke"]},"no-littering":{"a":"No Littering","b":"1F6AF","j":["forbidden","litter","no","not","prohibited","trash","bin","garbage","circle"]},"nonpotable-water":{"a":"Non-Potable Water","b":"1F6B1","j":["non-drinking","non-potable","water","non_potable_water","drink","faucet","tap","circle"]},"no-pedestrians":{"a":"No Pedestrians","b":"1F6B7","j":["forbidden","no","not","pedestrian","prohibited","rules","crossing","walking","circle"]},"no-mobile-phones":{"a":"No Mobile Phones","b":"1F4F5","j":["cell","forbidden","mobile","no","phone","iphone","mute","circle"]},"no-one-under-eighteen":{"a":"No One Under Eighteen","b":"1F51E","j":["18","age restriction","eighteen","prohibited","underage","drink","pub","night","minor","circle"]},"radioactive":{"a":"Radioactive","b":"2622","j":["sign","nuclear","danger"]},"biohazard":{"a":"Biohazard","b":"2623","j":["sign","danger"]},"up-arrow":{"a":"Up Arrow","b":"2B06","j":["arrow","cardinal","direction","north","blue-square","continue","top"]},"upright-arrow":{"a":"Up-Right Arrow","b":"2197","j":["arrow","direction","intercardinal","northeast","up-right arrow","up_right_arrow","blue-square","point","diagonal"]},"right-arrow":{"a":"Right Arrow","b":"27A1","j":["arrow","cardinal","direction","east","blue-square","next"]},"downright-arrow":{"a":"Down-Right Arrow","b":"2198","j":["arrow","direction","down-right arrow","intercardinal","southeast","down_right_arrow","blue-square","diagonal"]},"down-arrow":{"a":"Down Arrow","b":"2B07","j":["arrow","cardinal","direction","down","south","blue-square","bottom"]},"downleft-arrow":{"a":"Down-Left Arrow","b":"2199","j":["arrow","direction","down-left arrow","intercardinal","southwest","down_left_arrow","blue-square","diagonal"]},"left-arrow":{"a":"Left Arrow","b":"2B05","j":["arrow","cardinal","direction","west","blue-square","previous","back"]},"upleft-arrow":{"a":"Up-Left Arrow","b":"2196","j":["arrow","direction","intercardinal","northwest","up-left arrow","up_left_arrow","blue-square","point","diagonal"]},"updown-arrow":{"a":"Up-Down Arrow","b":"2195","j":["arrow","up-down arrow","up_down_arrow","blue-square","direction","way","vertical"]},"leftright-arrow":{"a":"Left-Right Arrow","b":"2194","j":["arrow","left-right arrow","left_right_arrow","shape","direction","horizontal","sideways"]},"right-arrow-curving-left":{"a":"Right Arrow Curving Left","b":"21A9","j":["arrow","back","return","blue-square","undo","enter"]},"left-arrow-curving-right":{"a":"Left Arrow Curving Right","b":"21AA","j":["arrow","blue-square","return","rotate","direction"]},"right-arrow-curving-up":{"a":"Right Arrow Curving Up","b":"2934","j":["arrow","blue-square","direction","top"]},"right-arrow-curving-down":{"a":"Right Arrow Curving Down","b":"2935","j":["arrow","down","blue-square","direction","bottom"]},"clockwise-vertical-arrows":{"a":"Clockwise Vertical Arrows","b":"1F503","j":["arrow","clockwise","reload","sync","cycle","round","repeat"]},"counterclockwise-arrows-button":{"a":"Counterclockwise Arrows Button","b":"1F504","j":["anticlockwise","arrow","counterclockwise","withershins","blue-square","sync","cycle"]},"back-arrow":{"a":"Back Arrow","b":"1F519","j":["arrow","back","BACK arrow","words","return"]},"end-arrow":{"a":"End Arrow","b":"1F51A","j":["arrow","end","END arrow","words"]},"on-arrow":{"a":"On! Arrow","b":"1F51B","j":["arrow","mark","on","ON! arrow","words"]},"soon-arrow":{"a":"Soon Arrow","b":"1F51C","j":["arrow","soon","SOON arrow","words"]},"top-arrow":{"a":"Top Arrow","b":"1F51D","j":["arrow","top","TOP arrow","up","words","blue-square"]},"place-of-worship":{"a":"Place of Worship","b":"1F6D0","j":["religion","worship","church","temple","prayer"]},"atom-symbol":{"a":"Atom Symbol","b":"269B","j":["atheist","atom","science","physics","chemistry"]},"om":{"a":"Om","b":"1F549","j":["Hindu","religion","hinduism","buddhism","sikhism","jainism"]},"star-of-david":{"a":"Star of David","b":"2721","j":["David","Jew","Jewish","religion","star","star of David","judaism"]},"wheel-of-dharma":{"a":"Wheel of Dharma","b":"2638","j":["Buddhist","dharma","religion","wheel","hinduism","buddhism","sikhism","jainism"]},"yin-yang":{"a":"Yin Yang","b":"262F","j":["religion","tao","taoist","yang","yin","balance"]},"latin-cross":{"a":"Latin Cross","b":"271D","j":["Christian","cross","religion","christianity"]},"orthodox-cross":{"a":"Orthodox Cross","b":"2626","j":["Christian","cross","religion","suppedaneum"]},"star-and-crescent":{"a":"Star and Crescent","b":"262A","j":["islam","Muslim","religion"]},"peace-symbol":{"a":"Peace Symbol","b":"262E","j":["peace","hippie"]},"menorah":{"a":"Menorah","b":"1F54E","j":["candelabrum","candlestick","religion","hanukkah","candles","jewish"]},"dotted-sixpointed-star":{"a":"Dotted Six-Pointed Star","b":"1F52F","j":["dotted six-pointed star","fortune","star","dotted_six_pointed_star","purple-square","religion","jewish","hexagram"]},"aries":{"a":"Aries","b":"2648","j":["ram","zodiac","sign","purple-square","astrology"]},"taurus":{"a":"Taurus","b":"2649","j":["bull","ox","zodiac","purple-square","sign","astrology"]},"gemini":{"a":"Gemini","b":"264A","j":["twins","zodiac","sign","purple-square","astrology"]},"cancer":{"a":"Cancer","b":"264B","j":["crab","zodiac","sign","purple-square","astrology"]},"leo":{"a":"Leo","b":"264C","j":["lion","zodiac","sign","purple-square","astrology"]},"virgo":{"a":"Virgo","b":"264D","j":["zodiac","sign","purple-square","astrology"]},"libra":{"a":"Libra","b":"264E","j":["balance","justice","scales","zodiac","sign","purple-square","astrology"]},"scorpio":{"a":"Scorpio","b":"264F","j":["scorpion","scorpius","zodiac","sign","purple-square","astrology"]},"sagittarius":{"a":"Sagittarius","b":"2650","j":["archer","zodiac","sign","purple-square","astrology"]},"capricorn":{"a":"Capricorn","b":"2651","j":["goat","zodiac","sign","purple-square","astrology"]},"aquarius":{"a":"Aquarius","b":"2652","j":["bearer","water","zodiac","sign","purple-square","astrology"]},"pisces":{"a":"Pisces","b":"2653","j":["fish","zodiac","purple-square","sign","astrology"]},"ophiuchus":{"a":"Ophiuchus","b":"26CE","j":["bearer","serpent","snake","zodiac","sign","purple-square","constellation","astrology"]},"shuffle-tracks-button":{"a":"Shuffle Tracks Button","b":"1F500","j":["arrow","crossed","blue-square","shuffle","music","random"]},"repeat-button":{"a":"Repeat Button","b":"1F501","j":["arrow","clockwise","repeat","loop","record"]},"repeat-single-button":{"a":"Repeat Single Button","b":"1F502","j":["arrow","clockwise","once","blue-square","loop"]},"play-button":{"a":"Play Button","b":"25B6","j":["arrow","play","right","triangle","blue-square","direction"]},"fastforward-button":{"a":"Fast-Forward Button","b":"23E9","j":["arrow","double","fast","fast-forward button","forward","fast_forward_button","blue-square","play","speed","continue"]},"next-track-button":{"a":"Next Track Button","b":"23ED","j":["arrow","next scene","next track","triangle","forward","next","blue-square"]},"play-or-pause-button":{"a":"Play or Pause Button","b":"23EF","j":["arrow","pause","play","right","triangle","blue-square"]},"reverse-button":{"a":"Reverse Button","b":"25C0","j":["arrow","left","reverse","triangle","blue-square","direction"]},"fast-reverse-button":{"a":"Fast Reverse Button","b":"23EA","j":["arrow","double","rewind","play","blue-square"]},"last-track-button":{"a":"Last Track Button","b":"23EE","j":["arrow","previous scene","previous track","triangle","backward"]},"upwards-button":{"a":"Upwards Button","b":"1F53C","j":["arrow","button","red","blue-square","triangle","direction","point","forward","top"]},"fast-up-button":{"a":"Fast Up Button","b":"23EB","j":["arrow","double","blue-square","direction","top"]},"downwards-button":{"a":"Downwards Button","b":"1F53D","j":["arrow","button","down","red","blue-square","direction","bottom"]},"fast-down-button":{"a":"Fast Down Button","b":"23EC","j":["arrow","double","down","blue-square","direction","bottom"]},"pause-button":{"a":"Pause Button","b":"23F8","j":["bar","double","pause","vertical","blue-square"]},"stop-button":{"a":"Stop Button","b":"23F9","j":["square","stop","blue-square"]},"record-button":{"a":"Record Button","b":"23FA","j":["circle","record","blue-square"]},"eject-button":{"a":"Eject Button","b":"23CF","j":["eject","blue-square"]},"cinema":{"a":"Cinema","b":"1F3A6","j":["camera","film","movie","blue-square","record","curtain","stage","theater"]},"dim-button":{"a":"Dim Button","b":"1F505","j":["brightness","dim","low","sun","afternoon","warm","summer"]},"bright-button":{"a":"Bright Button","b":"1F506","j":["bright","brightness","sun","light"]},"antenna-bars":{"a":"Antenna Bars","b":"1F4F6","j":["antenna","bar","cell","mobile","phone","blue-square","reception","internet","connection","wifi","bluetooth","bars"]},"vibration-mode":{"a":"Vibration Mode","b":"1F4F3","j":["cell","mobile","mode","phone","telephone","vibration","orange-square"]},"mobile-phone-off":{"a":"Mobile Phone off","b":"1F4F4","j":["cell","mobile","off","phone","telephone","mute","orange-square","silence","quiet"]},"female-sign":{"a":"Female Sign","b":"2640","j":["woman","women","lady","girl"]},"male-sign":{"a":"Male Sign","b":"2642","j":["man","boy","men"]},"transgender-symbol":{"a":"Transgender Symbol","b":"26A7","j":["transgender","lgbtq"]},"multiply":{"a":"Multiply","b":"2716","j":["×","cancel","multiplication","sign","x","multiplication_sign","math","calculation"]},"plus":{"a":"Plus","b":"2795","j":["+","math","sign","plus_sign","calculation","addition","more","increase"]},"minus":{"a":"Minus","b":"2796","j":["-","−","math","sign","minus_sign","calculation","subtract","less"]},"divide":{"a":"Divide","b":"2797","j":["÷","division","math","sign","division_sign","calculation"]},"infinity":{"a":"Infinity","b":"267E","j":["forever","unbounded","universal"]},"double-exclamation-mark":{"a":"Double Exclamation Mark","b":"203C","j":["!","!!","bangbang","exclamation","mark","surprise"]},"exclamation-question-mark":{"a":"Exclamation Question Mark","b":"2049","j":["!","!?","?","exclamation","interrobang","mark","punctuation","question","wat","surprise"]},"red-question-mark":{"a":"Red Question Mark","b":"2753","j":["?","mark","punctuation","question","question_mark","doubt","confused"]},"white-question-mark":{"a":"White Question Mark","b":"2754","j":["?","mark","outlined","punctuation","question","doubts","gray","huh","confused"]},"white-exclamation-mark":{"a":"White Exclamation Mark","b":"2755","j":["!","exclamation","mark","outlined","punctuation","surprise","gray","wow","warning"]},"red-exclamation-mark":{"a":"Red Exclamation Mark","b":"2757","j":["!","exclamation","mark","punctuation","exclamation_mark","heavy_exclamation_mark","danger","surprise","wow","warning"]},"wavy-dash":{"a":"Wavy Dash","b":"3030","j":["dash","punctuation","wavy","draw","line","moustache","mustache","squiggle","scribble"]},"currency-exchange":{"a":"Currency Exchange","b":"1F4B1","j":["bank","currency","exchange","money","sales","dollar","travel"]},"heavy-dollar-sign":{"a":"Heavy Dollar Sign","b":"1F4B2","j":["currency","dollar","money","sales","payment","buck"]},"medical-symbol":{"a":"Medical Symbol","b":"2695","j":["aesculapius","medicine","staff","health","hospital"]},"recycling-symbol":{"a":"Recycling Symbol","b":"267B","j":["recycle","arrow","environment","garbage","trash"]},"fleurdelis":{"a":"Fleur-De-Lis","b":"269C","j":["fleur-de-lis","fleur_de_lis","decorative","scout"]},"trident-emblem":{"a":"Trident Emblem","b":"1F531","j":["anchor","emblem","ship","tool","trident","weapon","spear"]},"name-badge":{"a":"Name Badge","b":"1F4DB","j":["badge","name","fire","forbid"]},"japanese-symbol-for-beginner":{"a":"Japanese Symbol for Beginner","b":"1F530","j":["beginner","chevron","Japanese","Japanese symbol for beginner","leaf","badge","shield"]},"hollow-red-circle":{"a":"Hollow Red Circle","b":"2B55","j":["circle","large","o","red","round"]},"check-mark-button":{"a":"Check Mark Button","b":"2705","j":["✓","button","check","mark","green-square","ok","agree","vote","election","answer","tick"]},"check-box-with-check":{"a":"Check Box with Check","b":"2611","j":["✓","box","check","ok","agree","confirm","black-square","vote","election","yes","tick"]},"check-mark":{"a":"Check Mark","b":"2714","j":["✓","check","mark","ok","nike","answer","yes","tick"]},"cross-mark":{"a":"Cross Mark","b":"274C","j":["×","cancel","cross","mark","multiplication","multiply","x","no","delete","remove","red"]},"cross-mark-button":{"a":"Cross Mark Button","b":"274E","j":["×","mark","square","x","green-square","no","deny"]},"curly-loop":{"a":"Curly Loop","b":"27B0","j":["curl","loop","scribble","draw","shape","squiggle"]},"double-curly-loop":{"a":"Double Curly Loop","b":"27BF","j":["curl","double","loop","tape","cassette"]},"part-alternation-mark":{"a":"Part Alternation Mark","b":"303D","j":["mark","part","graph","presentation","stats","business","economics","bad"]},"eightspoked-asterisk":{"a":"Eight-Spoked Asterisk","b":"2733","j":["*","asterisk","eight-spoked asterisk","eight_spoked_asterisk","star","sparkle","green-square"]},"eightpointed-star":{"a":"Eight-Pointed Star","b":"2734","j":["*","eight-pointed star","star","eight_pointed_star","orange-square","shape","polygon"]},"sparkle":{"a":"Sparkle","b":"2747","j":["*","stars","green-square","awesome","good","fireworks"]},"copyright":{"a":"Copyright","b":"00A9","j":["c","ip","license","circle","law","legal"]},"registered":{"a":"Registered","b":"00AE","j":["r","alphabet","circle"]},"trade-mark":{"a":"Trade Mark","b":"2122","j":["mark","tm","trademark","brand","law","legal"]},"keycap":{"a":"Keycap: *","b":"002A-FE0F-20E3","j":["keycap_","star"]},"keycap-0":{"a":"Keycap: 0","b":"0030-FE0F-20E3","j":["keycap","0","numbers","blue-square","null"]},"keycap-1":{"a":"Keycap: 1","b":"0031-FE0F-20E3","j":["keycap","blue-square","numbers","1"]},"keycap-2":{"a":"Keycap: 2","b":"0032-FE0F-20E3","j":["keycap","numbers","2","prime","blue-square"]},"keycap-3":{"a":"Keycap: 3","b":"0033-FE0F-20E3","j":["keycap","3","numbers","prime","blue-square"]},"keycap-4":{"a":"Keycap: 4","b":"0034-FE0F-20E3","j":["keycap","4","numbers","blue-square"]},"keycap-5":{"a":"Keycap: 5","b":"0035-FE0F-20E3","j":["keycap","5","numbers","blue-square","prime"]},"keycap-6":{"a":"Keycap: 6","b":"0036-FE0F-20E3","j":["keycap","6","numbers","blue-square"]},"keycap-7":{"a":"Keycap: 7","b":"0037-FE0F-20E3","j":["keycap","7","numbers","blue-square","prime"]},"keycap-8":{"a":"Keycap: 8","b":"0038-FE0F-20E3","j":["keycap","8","blue-square","numbers"]},"keycap-9":{"a":"Keycap: 9","b":"0039-FE0F-20E3","j":["keycap","blue-square","numbers","9"]},"keycap-10":{"a":"Keycap: 10","b":"1F51F","j":["keycap","numbers","10","blue-square"]},"input-latin-uppercase":{"a":"Input Latin Uppercase","b":"1F520","j":["ABCD","input","latin","letters","uppercase","alphabet","words","blue-square"]},"input-latin-lowercase":{"a":"Input Latin Lowercase","b":"1F521","j":["abcd","input","latin","letters","lowercase","blue-square","alphabet"]},"input-numbers":{"a":"Input Numbers","b":"1F522","j":["1234","input","numbers","blue-square"]},"input-symbols":{"a":"Input Symbols","b":"1F523","j":["〒♪&%","input","blue-square","music","note","ampersand","percent","glyphs","characters"]},"input-latin-letters":{"a":"Input Latin Letters","b":"1F524","j":["abc","alphabet","input","latin","letters","blue-square"]},"a-button-blood-type":{"a":"A Button (Blood Type)","b":"1F170","j":["a","A button (blood type)","blood type","a_button","red-square","alphabet","letter"]},"ab-button-blood-type":{"a":"Ab Button (Blood Type)","b":"1F18E","j":["ab","AB button (blood type)","blood type","ab_button","red-square","alphabet"]},"b-button-blood-type":{"a":"B Button (Blood Type)","b":"1F171","j":["b","B button (blood type)","blood type","b_button","red-square","alphabet","letter"]},"cl-button":{"a":"Cl Button","b":"1F191","j":["cl","CL button","alphabet","words","red-square"]},"cool-button":{"a":"Cool Button","b":"1F192","j":["cool","COOL button","words","blue-square"]},"free-button":{"a":"Free Button","b":"1F193","j":["free","FREE button","blue-square","words"]},"information":{"a":"Information","b":"2139","j":["i","blue-square","alphabet","letter"]},"id-button":{"a":"Id Button","b":"1F194","j":["id","ID button","identity","purple-square","words"]},"circled-m":{"a":"Circled M","b":"24C2","j":["circle","circled M","m","alphabet","blue-circle","letter"]},"new-button":{"a":"New Button","b":"1F195","j":["new","NEW button","blue-square","words","start"]},"ng-button":{"a":"Ng Button","b":"1F196","j":["ng","NG button","blue-square","words","shape","icon"]},"o-button-blood-type":{"a":"O Button (Blood Type)","b":"1F17E","j":["blood type","o","O button (blood type)","o_button","alphabet","red-square","letter"]},"ok-button":{"a":"Ok Button","b":"1F197","j":["OK","OK button","good","agree","yes","blue-square"]},"p-button":{"a":"P Button","b":"1F17F","j":["P button","parking","cars","blue-square","alphabet","letter"]},"sos-button":{"a":"Sos Button","b":"1F198","j":["help","sos","SOS button","red-square","words","emergency","911"]},"up-button":{"a":"Up! Button","b":"1F199","j":["mark","up","UP! button","blue-square","above","high"]},"vs-button":{"a":"Vs Button","b":"1F19A","j":["versus","vs","VS button","words","orange-square"]},"japanese-here-button":{"a":"Japanese “Here” Button","b":"1F201","j":["“here”","Japanese","Japanese “here” button","katakana","ココ","blue-square","here","japanese","destination"]},"japanese-service-charge-button":{"a":"Japanese “Service Charge” Button","b":"1F202","j":["“service charge”","Japanese","Japanese “service charge” button","katakana","サ","japanese","blue-square"]},"japanese-monthly-amount-button":{"a":"Japanese “Monthly Amount” Button","b":"1F237","j":["“monthly amount”","ideograph","Japanese","Japanese “monthly amount” button","月","chinese","month","moon","japanese","orange-square","kanji"]},"japanese-not-free-of-charge-button":{"a":"Japanese “Not Free of Charge” Button","b":"1F236","j":["“not free of charge”","ideograph","Japanese","Japanese “not free of charge” button","有","orange-square","chinese","have","kanji"]},"japanese-reserved-button":{"a":"Japanese “Reserved” Button","b":"1F22F","j":["“reserved”","ideograph","Japanese","Japanese “reserved” button","指","chinese","point","green-square","kanji"]},"japanese-bargain-button":{"a":"Japanese “Bargain” Button","b":"1F250","j":["“bargain”","ideograph","Japanese","Japanese “bargain” button","得","chinese","kanji","obtain","get","circle"]},"japanese-discount-button":{"a":"Japanese “Discount” Button","b":"1F239","j":["“discount”","ideograph","Japanese","Japanese “discount” button","割","cut","divide","chinese","kanji","pink-square"]},"japanese-free-of-charge-button":{"a":"Japanese “Free of Charge” Button","b":"1F21A","j":["“free of charge”","ideograph","Japanese","Japanese “free of charge” button","無","nothing","chinese","kanji","japanese","orange-square"]},"japanese-prohibited-button":{"a":"Japanese “Prohibited” Button","b":"1F232","j":["“prohibited”","ideograph","Japanese","Japanese “prohibited” button","禁","kanji","japanese","chinese","forbidden","limit","restricted","red-square"]},"japanese-acceptable-button":{"a":"Japanese “Acceptable” Button","b":"1F251","j":["“acceptable”","ideograph","Japanese","Japanese “acceptable” button","可","ok","good","chinese","kanji","agree","yes","orange-circle"]},"japanese-application-button":{"a":"Japanese “Application” Button","b":"1F238","j":["“application”","ideograph","Japanese","Japanese “application” button","申","chinese","japanese","kanji","orange-square"]},"japanese-passing-grade-button":{"a":"Japanese “Passing Grade” Button","b":"1F234","j":["“passing grade”","ideograph","Japanese","Japanese “passing grade” button","合","japanese","chinese","join","kanji","red-square"]},"japanese-vacancy-button":{"a":"Japanese “Vacancy” Button","b":"1F233","j":["“vacancy”","ideograph","Japanese","Japanese “vacancy” button","空","kanji","japanese","chinese","empty","sky","blue-square"]},"japanese-congratulations-button":{"a":"Japanese “Congratulations” Button","b":"3297","j":["“congratulations”","ideograph","Japanese","Japanese “congratulations” button","祝","chinese","kanji","japanese","red-circle"]},"japanese-secret-button":{"a":"Japanese “Secret” Button","b":"3299","j":["“secret”","ideograph","Japanese","Japanese “secret” button","秘","privacy","chinese","sshh","kanji","red-circle"]},"japanese-open-for-business-button":{"a":"Japanese “Open for Business” Button","b":"1F23A","j":["“open for business”","ideograph","Japanese","Japanese “open for business” button","営","japanese","opening hours","orange-square"]},"japanese-no-vacancy-button":{"a":"Japanese “No Vacancy” Button","b":"1F235","j":["“no vacancy”","ideograph","Japanese","Japanese “no vacancy” button","満","full","chinese","japanese","red-square","kanji"]},"red-circle":{"a":"Red Circle","b":"1F534","j":["circle","geometric","red","shape","error","danger"]},"orange-circle":{"a":"Orange Circle","b":"1F7E0","j":["circle","orange","round"]},"yellow-circle":{"a":"Yellow Circle","b":"1F7E1","j":["circle","yellow","round"]},"green-circle":{"a":"Green Circle","b":"1F7E2","j":["circle","green","round"]},"blue-circle":{"a":"Blue Circle","b":"1F535","j":["blue","circle","geometric","shape","icon","button"]},"purple-circle":{"a":"Purple Circle","b":"1F7E3","j":["circle","purple","round"]},"brown-circle":{"a":"Brown Circle","b":"1F7E4","j":["brown","circle","round"]},"black-circle":{"a":"Black Circle","b":"26AB","j":["circle","geometric","shape","button","round"]},"white-circle":{"a":"White Circle","b":"26AA","j":["circle","geometric","shape","round"]},"red-square":{"a":"Red Square","b":"1F7E5","j":["red","square"]},"orange-square":{"a":"Orange Square","b":"1F7E7","j":["orange","square"]},"yellow-square":{"a":"Yellow Square","b":"1F7E8","j":["square","yellow"]},"green-square":{"a":"Green Square","b":"1F7E9","j":["green","square"]},"blue-square":{"a":"Blue Square","b":"1F7E6","j":["blue","square"]},"purple-square":{"a":"Purple Square","b":"1F7EA","j":["purple","square"]},"brown-square":{"a":"Brown Square","b":"1F7EB","j":["brown","square"]},"black-large-square":{"a":"Black Large Square","b":"2B1B","j":["geometric","square","shape","icon","button"]},"white-large-square":{"a":"White Large Square","b":"2B1C","j":["geometric","square","shape","icon","stone","button"]},"black-medium-square":{"a":"Black Medium Square","b":"25FC","j":["geometric","square","shape","button","icon"]},"white-medium-square":{"a":"White Medium Square","b":"25FB","j":["geometric","square","shape","stone","icon"]},"black-mediumsmall-square":{"a":"Black Medium-Small Square","b":"25FE","j":["black medium-small square","geometric","square","black_medium_small_square","icon","shape","button"]},"white-mediumsmall-square":{"a":"White Medium-Small Square","b":"25FD","j":["geometric","square","white medium-small square","white_medium_small_square","shape","stone","icon","button"]},"black-small-square":{"a":"Black Small Square","b":"25AA","j":["geometric","square","shape","icon"]},"white-small-square":{"a":"White Small Square","b":"25AB","j":["geometric","square","shape","icon"]},"large-orange-diamond":{"a":"Large Orange Diamond","b":"1F536","j":["diamond","geometric","orange","shape","jewel","gem"]},"large-blue-diamond":{"a":"Large Blue Diamond","b":"1F537","j":["blue","diamond","geometric","shape","jewel","gem"]},"small-orange-diamond":{"a":"Small Orange Diamond","b":"1F538","j":["diamond","geometric","orange","shape","jewel","gem"]},"small-blue-diamond":{"a":"Small Blue Diamond","b":"1F539","j":["blue","diamond","geometric","shape","jewel","gem"]},"red-triangle-pointed-up":{"a":"Red Triangle Pointed Up","b":"1F53A","j":["geometric","red","shape","direction","up","top"]},"red-triangle-pointed-down":{"a":"Red Triangle Pointed Down","b":"1F53B","j":["down","geometric","red","shape","direction","bottom"]},"diamond-with-a-dot":{"a":"Diamond with a Dot","b":"1F4A0","j":["comic","diamond","geometric","inside","jewel","blue","gem","crystal","fancy"]},"radio-button":{"a":"Radio Button","b":"1F518","j":["button","geometric","radio","input","old","music","circle"]},"white-square-button":{"a":"White Square Button","b":"1F533","j":["button","geometric","outlined","square","shape","input"]},"black-square-button":{"a":"Black Square Button","b":"1F532","j":["button","geometric","square","shape","input","frame"]},"chequered-flag":{"a":"Chequered Flag","b":"1F3C1","j":["checkered","chequered","racing","contest","finishline","race","gokart"]},"triangular-flag":{"a":"Triangular Flag","b":"1F6A9","j":["post","mark","milestone","place"]},"crossed-flags":{"a":"Crossed Flags","b":"1F38C","j":["celebration","cross","crossed","Japanese","japanese","nation","country","border"]},"black-flag":{"a":"Black Flag","b":"1F3F4","j":["waving","pirate"]},"white-flag":{"a":"White Flag","b":"1F3F3","j":["waving","losing","loser","lost","surrender","give up","fail"]},"rainbow-flag":{"a":"Rainbow Flag","b":"1F3F3-FE0F-200D-1F308","j":["pride","rainbow","flag","gay","lgbt","glbt","queer","homosexual","lesbian","bisexual","transgender"]},"transgender-flag":{"a":"Transgender Flag","b":"1F3F3-FE0F-200D-26A7-FE0F","j":["flag","light blue","pink","transgender","white","lgbtq"]},"pirate-flag":{"a":"Pirate Flag","b":"1F3F4-200D-2620-FE0F","j":["Jolly Roger","pirate","plunder","treasure","skull","crossbones","flag","banner"]},"flag-ascension-island":{"a":"Flag: Ascension Island","b":"1F1E6-1F1E8","j":["flag"]},"flag-andorra":{"a":"Flag: Andorra","b":"1F1E6-1F1E9","j":["flag","ad","nation","country","banner"]},"flag-united-arab-emirates":{"a":"Flag: United Arab Emirates","b":"1F1E6-1F1EA","j":["flag","united","arab","emirates","nation","country","banner"]},"flag-afghanistan":{"a":"Flag: Afghanistan","b":"1F1E6-1F1EB","j":["flag","af","nation","country","banner"]},"flag-antigua--barbuda":{"a":"Flag: Antigua & Barbuda","b":"1F1E6-1F1EC","j":["flag","flag_antigua_barbuda","antigua","barbuda","nation","country","banner"]},"flag-anguilla":{"a":"Flag: Anguilla","b":"1F1E6-1F1EE","j":["flag","ai","nation","country","banner"]},"flag-albania":{"a":"Flag: Albania","b":"1F1E6-1F1F1","j":["flag","al","nation","country","banner"]},"flag-armenia":{"a":"Flag: Armenia","b":"1F1E6-1F1F2","j":["flag","am","nation","country","banner"]},"flag-angola":{"a":"Flag: Angola","b":"1F1E6-1F1F4","j":["flag","ao","nation","country","banner"]},"flag-antarctica":{"a":"Flag: Antarctica","b":"1F1E6-1F1F6","j":["flag","aq","nation","country","banner"]},"flag-argentina":{"a":"Flag: Argentina","b":"1F1E6-1F1F7","j":["flag","ar","nation","country","banner"]},"flag-american-samoa":{"a":"Flag: American Samoa","b":"1F1E6-1F1F8","j":["flag","american","ws","nation","country","banner"]},"flag-austria":{"a":"Flag: Austria","b":"1F1E6-1F1F9","j":["flag","at","nation","country","banner"]},"flag-australia":{"a":"Flag: Australia","b":"1F1E6-1F1FA","j":["flag","au","nation","country","banner"]},"flag-aruba":{"a":"Flag: Aruba","b":"1F1E6-1F1FC","j":["flag","aw","nation","country","banner"]},"flag-land-islands":{"a":"Flag: Åland Islands","b":"1F1E6-1F1FD","j":["flag","flag_aland_islands","Åland","islands","nation","country","banner"]},"flag-azerbaijan":{"a":"Flag: Azerbaijan","b":"1F1E6-1F1FF","j":["flag","az","nation","country","banner"]},"flag-bosnia--herzegovina":{"a":"Flag: Bosnia & Herzegovina","b":"1F1E7-1F1E6","j":["flag","flag_bosnia_herzegovina","bosnia","herzegovina","nation","country","banner"]},"flag-barbados":{"a":"Flag: Barbados","b":"1F1E7-1F1E7","j":["flag","bb","nation","country","banner"]},"flag-bangladesh":{"a":"Flag: Bangladesh","b":"1F1E7-1F1E9","j":["flag","bd","nation","country","banner"]},"flag-belgium":{"a":"Flag: Belgium","b":"1F1E7-1F1EA","j":["flag","be","nation","country","banner"]},"flag-burkina-faso":{"a":"Flag: Burkina Faso","b":"1F1E7-1F1EB","j":["flag","burkina","faso","nation","country","banner"]},"flag-bulgaria":{"a":"Flag: Bulgaria","b":"1F1E7-1F1EC","j":["flag","bg","nation","country","banner"]},"flag-bahrain":{"a":"Flag: Bahrain","b":"1F1E7-1F1ED","j":["flag","bh","nation","country","banner"]},"flag-burundi":{"a":"Flag: Burundi","b":"1F1E7-1F1EE","j":["flag","bi","nation","country","banner"]},"flag-benin":{"a":"Flag: Benin","b":"1F1E7-1F1EF","j":["flag","bj","nation","country","banner"]},"flag-st-barthlemy":{"a":"Flag: St. Barthélemy","b":"1F1E7-1F1F1","j":["flag","flag_st_barthelemy","saint","barthélemy","nation","country","banner"]},"flag-bermuda":{"a":"Flag: Bermuda","b":"1F1E7-1F1F2","j":["flag","bm","nation","country","banner"]},"flag-brunei":{"a":"Flag: Brunei","b":"1F1E7-1F1F3","j":["flag","bn","darussalam","nation","country","banner"]},"flag-bolivia":{"a":"Flag: Bolivia","b":"1F1E7-1F1F4","j":["flag","bo","nation","country","banner"]},"flag-caribbean-netherlands":{"a":"Flag: Caribbean Netherlands","b":"1F1E7-1F1F6","j":["flag","bonaire","nation","country","banner"]},"flag-brazil":{"a":"Flag: Brazil","b":"1F1E7-1F1F7","j":["flag","br","nation","country","banner"]},"flag-bahamas":{"a":"Flag: Bahamas","b":"1F1E7-1F1F8","j":["flag","bs","nation","country","banner"]},"flag-bhutan":{"a":"Flag: Bhutan","b":"1F1E7-1F1F9","j":["flag","bt","nation","country","banner"]},"flag-bouvet-island":{"a":"Flag: Bouvet Island","b":"1F1E7-1F1FB","j":["flag","norway"]},"flag-botswana":{"a":"Flag: Botswana","b":"1F1E7-1F1FC","j":["flag","bw","nation","country","banner"]},"flag-belarus":{"a":"Flag: Belarus","b":"1F1E7-1F1FE","j":["flag","by","nation","country","banner"]},"flag-belize":{"a":"Flag: Belize","b":"1F1E7-1F1FF","j":["flag","bz","nation","country","banner"]},"flag-canada":{"a":"Flag: Canada","b":"1F1E8-1F1E6","j":["flag","ca","nation","country","banner"]},"flag-cocos-keeling-islands":{"a":"Flag: Cocos (Keeling) Islands","b":"1F1E8-1F1E8","j":["flag","flag_cocos_islands","cocos","keeling","islands","nation","country","banner"]},"flag-congo--kinshasa":{"a":"Flag: Congo - Kinshasa","b":"1F1E8-1F1E9","j":["flag","flag_congo_kinshasa","congo","democratic","republic","nation","country","banner"]},"flag-central-african-republic":{"a":"Flag: Central African Republic","b":"1F1E8-1F1EB","j":["flag","central","african","republic","nation","country","banner"]},"flag-congo--brazzaville":{"a":"Flag: Congo - Brazzaville","b":"1F1E8-1F1EC","j":["flag","flag_congo_brazzaville","congo","nation","country","banner"]},"flag-switzerland":{"a":"Flag: Switzerland","b":"1F1E8-1F1ED","j":["flag","ch","nation","country","banner"]},"flag-cte-divoire":{"a":"Flag: Côte D’Ivoire","b":"1F1E8-1F1EE","j":["flag","flag_cote_d_ivoire","ivory","coast","nation","country","banner"]},"flag-cook-islands":{"a":"Flag: Cook Islands","b":"1F1E8-1F1F0","j":["flag","cook","islands","nation","country","banner"]},"flag-chile":{"a":"Flag: Chile","b":"1F1E8-1F1F1","j":["flag","nation","country","banner"]},"flag-cameroon":{"a":"Flag: Cameroon","b":"1F1E8-1F1F2","j":["flag","cm","nation","country","banner"]},"flag-china":{"a":"Flag: China","b":"1F1E8-1F1F3","j":["flag","china","chinese","prc","country","nation","banner"]},"flag-colombia":{"a":"Flag: Colombia","b":"1F1E8-1F1F4","j":["flag","co","nation","country","banner"]},"flag-clipperton-island":{"a":"Flag: Clipperton Island","b":"1F1E8-1F1F5","j":["flag"]},"flag-costa-rica":{"a":"Flag: Costa Rica","b":"1F1E8-1F1F7","j":["flag","costa","rica","nation","country","banner"]},"flag-cuba":{"a":"Flag: Cuba","b":"1F1E8-1F1FA","j":["flag","cu","nation","country","banner"]},"flag-cape-verde":{"a":"Flag: Cape Verde","b":"1F1E8-1F1FB","j":["flag","cabo","verde","nation","country","banner"]},"flag-curaao":{"a":"Flag: Curaçao","b":"1F1E8-1F1FC","j":["flag","flag_curacao","curaçao","nation","country","banner"]},"flag-christmas-island":{"a":"Flag: Christmas Island","b":"1F1E8-1F1FD","j":["flag","christmas","island","nation","country","banner"]},"flag-cyprus":{"a":"Flag: Cyprus","b":"1F1E8-1F1FE","j":["flag","cy","nation","country","banner"]},"flag-czechia":{"a":"Flag: Czechia","b":"1F1E8-1F1FF","j":["flag","cz","nation","country","banner"]},"flag-germany":{"a":"Flag: Germany","b":"1F1E9-1F1EA","j":["flag","german","nation","country","banner"]},"flag-diego-garcia":{"a":"Flag: Diego Garcia","b":"1F1E9-1F1EC","j":["flag"]},"flag-djibouti":{"a":"Flag: Djibouti","b":"1F1E9-1F1EF","j":["flag","dj","nation","country","banner"]},"flag-denmark":{"a":"Flag: Denmark","b":"1F1E9-1F1F0","j":["flag","dk","nation","country","banner"]},"flag-dominica":{"a":"Flag: Dominica","b":"1F1E9-1F1F2","j":["flag","dm","nation","country","banner"]},"flag-dominican-republic":{"a":"Flag: Dominican Republic","b":"1F1E9-1F1F4","j":["flag","dominican","republic","nation","country","banner"]},"flag-algeria":{"a":"Flag: Algeria","b":"1F1E9-1F1FF","j":["flag","dz","nation","country","banner"]},"flag-ceuta--melilla":{"a":"Flag: Ceuta & Melilla","b":"1F1EA-1F1E6","j":["flag","flag_ceuta_melilla"]},"flag-ecuador":{"a":"Flag: Ecuador","b":"1F1EA-1F1E8","j":["flag","ec","nation","country","banner"]},"flag-estonia":{"a":"Flag: Estonia","b":"1F1EA-1F1EA","j":["flag","ee","nation","country","banner"]},"flag-egypt":{"a":"Flag: Egypt","b":"1F1EA-1F1EC","j":["flag","eg","nation","country","banner"]},"flag-western-sahara":{"a":"Flag: Western Sahara","b":"1F1EA-1F1ED","j":["flag","western","sahara","nation","country","banner"]},"flag-eritrea":{"a":"Flag: Eritrea","b":"1F1EA-1F1F7","j":["flag","er","nation","country","banner"]},"flag-spain":{"a":"Flag: Spain","b":"1F1EA-1F1F8","j":["flag","spain","nation","country","banner"]},"flag-ethiopia":{"a":"Flag: Ethiopia","b":"1F1EA-1F1F9","j":["flag","et","nation","country","banner"]},"flag-european-union":{"a":"Flag: European Union","b":"1F1EA-1F1FA","j":["flag","european","union","banner"]},"flag-finland":{"a":"Flag: Finland","b":"1F1EB-1F1EE","j":["flag","fi","nation","country","banner"]},"flag-fiji":{"a":"Flag: Fiji","b":"1F1EB-1F1EF","j":["flag","fj","nation","country","banner"]},"flag-falkland-islands":{"a":"Flag: Falkland Islands","b":"1F1EB-1F1F0","j":["flag","falkland","islands","malvinas","nation","country","banner"]},"flag-micronesia":{"a":"Flag: Micronesia","b":"1F1EB-1F1F2","j":["flag","micronesia","federated","states","nation","country","banner"]},"flag-faroe-islands":{"a":"Flag: Faroe Islands","b":"1F1EB-1F1F4","j":["flag","faroe","islands","nation","country","banner"]},"flag-france":{"a":"Flag: France","b":"1F1EB-1F1F7","j":["flag","banner","nation","france","french","country"]},"flag-gabon":{"a":"Flag: Gabon","b":"1F1EC-1F1E6","j":["flag","ga","nation","country","banner"]},"flag-united-kingdom":{"a":"Flag: United Kingdom","b":"1F1EC-1F1E7","j":["flag","united","kingdom","great","britain","northern","ireland","nation","country","banner","british","UK","english","england","union jack"]},"flag-grenada":{"a":"Flag: Grenada","b":"1F1EC-1F1E9","j":["flag","gd","nation","country","banner"]},"flag-georgia":{"a":"Flag: Georgia","b":"1F1EC-1F1EA","j":["flag","ge","nation","country","banner"]},"flag-french-guiana":{"a":"Flag: French Guiana","b":"1F1EC-1F1EB","j":["flag","french","guiana","nation","country","banner"]},"flag-guernsey":{"a":"Flag: Guernsey","b":"1F1EC-1F1EC","j":["flag","gg","nation","country","banner"]},"flag-ghana":{"a":"Flag: Ghana","b":"1F1EC-1F1ED","j":["flag","gh","nation","country","banner"]},"flag-gibraltar":{"a":"Flag: Gibraltar","b":"1F1EC-1F1EE","j":["flag","gi","nation","country","banner"]},"flag-greenland":{"a":"Flag: Greenland","b":"1F1EC-1F1F1","j":["flag","gl","nation","country","banner"]},"flag-gambia":{"a":"Flag: Gambia","b":"1F1EC-1F1F2","j":["flag","gm","nation","country","banner"]},"flag-guinea":{"a":"Flag: Guinea","b":"1F1EC-1F1F3","j":["flag","gn","nation","country","banner"]},"flag-guadeloupe":{"a":"Flag: Guadeloupe","b":"1F1EC-1F1F5","j":["flag","gp","nation","country","banner"]},"flag-equatorial-guinea":{"a":"Flag: Equatorial Guinea","b":"1F1EC-1F1F6","j":["flag","equatorial","gn","nation","country","banner"]},"flag-greece":{"a":"Flag: Greece","b":"1F1EC-1F1F7","j":["flag","gr","nation","country","banner"]},"flag-south-georgia--south-sandwich-islands":{"a":"Flag: South Georgia & South Sandwich Islands","b":"1F1EC-1F1F8","j":["flag","flag_south_georgia_south_sandwich_islands","south","georgia","sandwich","islands","nation","country","banner"]},"flag-guatemala":{"a":"Flag: Guatemala","b":"1F1EC-1F1F9","j":["flag","gt","nation","country","banner"]},"flag-guam":{"a":"Flag: Guam","b":"1F1EC-1F1FA","j":["flag","gu","nation","country","banner"]},"flag-guineabissau":{"a":"Flag: Guinea-Bissau","b":"1F1EC-1F1FC","j":["flag","flag_guinea_bissau","gw","bissau","nation","country","banner"]},"flag-guyana":{"a":"Flag: Guyana","b":"1F1EC-1F1FE","j":["flag","gy","nation","country","banner"]},"flag-hong-kong-sar-china":{"a":"Flag: Hong Kong Sar China","b":"1F1ED-1F1F0","j":["flag","hong","kong","nation","country","banner"]},"flag-heard--mcdonald-islands":{"a":"Flag: Heard & Mcdonald Islands","b":"1F1ED-1F1F2","j":["flag","flag_heard_mcdonald_islands"]},"flag-honduras":{"a":"Flag: Honduras","b":"1F1ED-1F1F3","j":["flag","hn","nation","country","banner"]},"flag-croatia":{"a":"Flag: Croatia","b":"1F1ED-1F1F7","j":["flag","hr","nation","country","banner"]},"flag-haiti":{"a":"Flag: Haiti","b":"1F1ED-1F1F9","j":["flag","ht","nation","country","banner"]},"flag-hungary":{"a":"Flag: Hungary","b":"1F1ED-1F1FA","j":["flag","hu","nation","country","banner"]},"flag-canary-islands":{"a":"Flag: Canary Islands","b":"1F1EE-1F1E8","j":["flag","canary","islands","nation","country","banner"]},"flag-indonesia":{"a":"Flag: Indonesia","b":"1F1EE-1F1E9","j":["flag","nation","country","banner"]},"flag-ireland":{"a":"Flag: Ireland","b":"1F1EE-1F1EA","j":["flag","ie","nation","country","banner"]},"flag-israel":{"a":"Flag: Israel","b":"1F1EE-1F1F1","j":["flag","il","nation","country","banner"]},"flag-isle-of-man":{"a":"Flag: Isle of Man","b":"1F1EE-1F1F2","j":["flag","isle","man","nation","country","banner"]},"flag-india":{"a":"Flag: India","b":"1F1EE-1F1F3","j":["flag","in","nation","country","banner"]},"flag-british-indian-ocean-territory":{"a":"Flag: British Indian Ocean Territory","b":"1F1EE-1F1F4","j":["flag","british","indian","ocean","territory","nation","country","banner"]},"flag-iraq":{"a":"Flag: Iraq","b":"1F1EE-1F1F6","j":["flag","iq","nation","country","banner"]},"flag-iran":{"a":"Flag: Iran","b":"1F1EE-1F1F7","j":["flag","iran","islamic","republic","nation","country","banner"]},"flag-iceland":{"a":"Flag: Iceland","b":"1F1EE-1F1F8","j":["flag","is","nation","country","banner"]},"flag-italy":{"a":"Flag: Italy","b":"1F1EE-1F1F9","j":["flag","italy","nation","country","banner"]},"flag-jersey":{"a":"Flag: Jersey","b":"1F1EF-1F1EA","j":["flag","je","nation","country","banner"]},"flag-jamaica":{"a":"Flag: Jamaica","b":"1F1EF-1F1F2","j":["flag","jm","nation","country","banner"]},"flag-jordan":{"a":"Flag: Jordan","b":"1F1EF-1F1F4","j":["flag","jo","nation","country","banner"]},"flag-japan":{"a":"Flag: Japan","b":"1F1EF-1F1F5","j":["flag","japanese","nation","country","banner"]},"flag-kenya":{"a":"Flag: Kenya","b":"1F1F0-1F1EA","j":["flag","ke","nation","country","banner"]},"flag-kyrgyzstan":{"a":"Flag: Kyrgyzstan","b":"1F1F0-1F1EC","j":["flag","kg","nation","country","banner"]},"flag-cambodia":{"a":"Flag: Cambodia","b":"1F1F0-1F1ED","j":["flag","kh","nation","country","banner"]},"flag-kiribati":{"a":"Flag: Kiribati","b":"1F1F0-1F1EE","j":["flag","ki","nation","country","banner"]},"flag-comoros":{"a":"Flag: Comoros","b":"1F1F0-1F1F2","j":["flag","km","nation","country","banner"]},"flag-st-kitts--nevis":{"a":"Flag: St. Kitts & Nevis","b":"1F1F0-1F1F3","j":["flag","flag_st_kitts_nevis","saint","kitts","nevis","nation","country","banner"]},"flag-north-korea":{"a":"Flag: North Korea","b":"1F1F0-1F1F5","j":["flag","north","korea","nation","country","banner"]},"flag-south-korea":{"a":"Flag: South Korea","b":"1F1F0-1F1F7","j":["flag","south","korea","nation","country","banner"]},"flag-kuwait":{"a":"Flag: Kuwait","b":"1F1F0-1F1FC","j":["flag","kw","nation","country","banner"]},"flag-cayman-islands":{"a":"Flag: Cayman Islands","b":"1F1F0-1F1FE","j":["flag","cayman","islands","nation","country","banner"]},"flag-kazakhstan":{"a":"Flag: Kazakhstan","b":"1F1F0-1F1FF","j":["flag","kz","nation","country","banner"]},"flag-laos":{"a":"Flag: Laos","b":"1F1F1-1F1E6","j":["flag","lao","democratic","republic","nation","country","banner"]},"flag-lebanon":{"a":"Flag: Lebanon","b":"1F1F1-1F1E7","j":["flag","lb","nation","country","banner"]},"flag-st-lucia":{"a":"Flag: St. Lucia","b":"1F1F1-1F1E8","j":["flag","saint","lucia","nation","country","banner"]},"flag-liechtenstein":{"a":"Flag: Liechtenstein","b":"1F1F1-1F1EE","j":["flag","li","nation","country","banner"]},"flag-sri-lanka":{"a":"Flag: Sri Lanka","b":"1F1F1-1F1F0","j":["flag","sri","lanka","nation","country","banner"]},"flag-liberia":{"a":"Flag: Liberia","b":"1F1F1-1F1F7","j":["flag","lr","nation","country","banner"]},"flag-lesotho":{"a":"Flag: Lesotho","b":"1F1F1-1F1F8","j":["flag","ls","nation","country","banner"]},"flag-lithuania":{"a":"Flag: Lithuania","b":"1F1F1-1F1F9","j":["flag","lt","nation","country","banner"]},"flag-luxembourg":{"a":"Flag: Luxembourg","b":"1F1F1-1F1FA","j":["flag","lu","nation","country","banner"]},"flag-latvia":{"a":"Flag: Latvia","b":"1F1F1-1F1FB","j":["flag","lv","nation","country","banner"]},"flag-libya":{"a":"Flag: Libya","b":"1F1F1-1F1FE","j":["flag","ly","nation","country","banner"]},"flag-morocco":{"a":"Flag: Morocco","b":"1F1F2-1F1E6","j":["flag","ma","nation","country","banner"]},"flag-monaco":{"a":"Flag: Monaco","b":"1F1F2-1F1E8","j":["flag","mc","nation","country","banner"]},"flag-moldova":{"a":"Flag: Moldova","b":"1F1F2-1F1E9","j":["flag","moldova","republic","nation","country","banner"]},"flag-montenegro":{"a":"Flag: Montenegro","b":"1F1F2-1F1EA","j":["flag","me","nation","country","banner"]},"flag-st-martin":{"a":"Flag: St. Martin","b":"1F1F2-1F1EB","j":["flag"]},"flag-madagascar":{"a":"Flag: Madagascar","b":"1F1F2-1F1EC","j":["flag","mg","nation","country","banner"]},"flag-marshall-islands":{"a":"Flag: Marshall Islands","b":"1F1F2-1F1ED","j":["flag","marshall","islands","nation","country","banner"]},"flag-north-macedonia":{"a":"Flag: North Macedonia","b":"1F1F2-1F1F0","j":["flag","macedonia","nation","country","banner"]},"flag-mali":{"a":"Flag: Mali","b":"1F1F2-1F1F1","j":["flag","ml","nation","country","banner"]},"flag-myanmar-burma":{"a":"Flag: Myanmar (Burma)","b":"1F1F2-1F1F2","j":["flag","flag_myanmar","mm","nation","country","banner"]},"flag-mongolia":{"a":"Flag: Mongolia","b":"1F1F2-1F1F3","j":["flag","mn","nation","country","banner"]},"flag-macao-sar-china":{"a":"Flag: Macao Sar China","b":"1F1F2-1F1F4","j":["flag","macao","nation","country","banner"]},"flag-northern-mariana-islands":{"a":"Flag: Northern Mariana Islands","b":"1F1F2-1F1F5","j":["flag","northern","mariana","islands","nation","country","banner"]},"flag-martinique":{"a":"Flag: Martinique","b":"1F1F2-1F1F6","j":["flag","mq","nation","country","banner"]},"flag-mauritania":{"a":"Flag: Mauritania","b":"1F1F2-1F1F7","j":["flag","mr","nation","country","banner"]},"flag-montserrat":{"a":"Flag: Montserrat","b":"1F1F2-1F1F8","j":["flag","ms","nation","country","banner"]},"flag-malta":{"a":"Flag: Malta","b":"1F1F2-1F1F9","j":["flag","mt","nation","country","banner"]},"flag-mauritius":{"a":"Flag: Mauritius","b":"1F1F2-1F1FA","j":["flag","mu","nation","country","banner"]},"flag-maldives":{"a":"Flag: Maldives","b":"1F1F2-1F1FB","j":["flag","mv","nation","country","banner"]},"flag-malawi":{"a":"Flag: Malawi","b":"1F1F2-1F1FC","j":["flag","mw","nation","country","banner"]},"flag-mexico":{"a":"Flag: Mexico","b":"1F1F2-1F1FD","j":["flag","mx","nation","country","banner"]},"flag-malaysia":{"a":"Flag: Malaysia","b":"1F1F2-1F1FE","j":["flag","my","nation","country","banner"]},"flag-mozambique":{"a":"Flag: Mozambique","b":"1F1F2-1F1FF","j":["flag","mz","nation","country","banner"]},"flag-namibia":{"a":"Flag: Namibia","b":"1F1F3-1F1E6","j":["flag","na","nation","country","banner"]},"flag-new-caledonia":{"a":"Flag: New Caledonia","b":"1F1F3-1F1E8","j":["flag","new","caledonia","nation","country","banner"]},"flag-niger":{"a":"Flag: Niger","b":"1F1F3-1F1EA","j":["flag","ne","nation","country","banner"]},"flag-norfolk-island":{"a":"Flag: Norfolk Island","b":"1F1F3-1F1EB","j":["flag","norfolk","island","nation","country","banner"]},"flag-nigeria":{"a":"Flag: Nigeria","b":"1F1F3-1F1EC","j":["flag","nation","country","banner"]},"flag-nicaragua":{"a":"Flag: Nicaragua","b":"1F1F3-1F1EE","j":["flag","ni","nation","country","banner"]},"flag-netherlands":{"a":"Flag: Netherlands","b":"1F1F3-1F1F1","j":["flag","nl","nation","country","banner"]},"flag-norway":{"a":"Flag: Norway","b":"1F1F3-1F1F4","j":["flag","no","nation","country","banner"]},"flag-nepal":{"a":"Flag: Nepal","b":"1F1F3-1F1F5","j":["flag","np","nation","country","banner"]},"flag-nauru":{"a":"Flag: Nauru","b":"1F1F3-1F1F7","j":["flag","nr","nation","country","banner"]},"flag-niue":{"a":"Flag: Niue","b":"1F1F3-1F1FA","j":["flag","nu","nation","country","banner"]},"flag-new-zealand":{"a":"Flag: New Zealand","b":"1F1F3-1F1FF","j":["flag","new","zealand","nation","country","banner"]},"flag-oman":{"a":"Flag: Oman","b":"1F1F4-1F1F2","j":["flag","om_symbol","nation","country","banner"]},"flag-panama":{"a":"Flag: Panama","b":"1F1F5-1F1E6","j":["flag","pa","nation","country","banner"]},"flag-peru":{"a":"Flag: Peru","b":"1F1F5-1F1EA","j":["flag","pe","nation","country","banner"]},"flag-french-polynesia":{"a":"Flag: French Polynesia","b":"1F1F5-1F1EB","j":["flag","french","polynesia","nation","country","banner"]},"flag-papua-new-guinea":{"a":"Flag: Papua New Guinea","b":"1F1F5-1F1EC","j":["flag","papua","new","guinea","nation","country","banner"]},"flag-philippines":{"a":"Flag: Philippines","b":"1F1F5-1F1ED","j":["flag","ph","nation","country","banner"]},"flag-pakistan":{"a":"Flag: Pakistan","b":"1F1F5-1F1F0","j":["flag","pk","nation","country","banner"]},"flag-poland":{"a":"Flag: Poland","b":"1F1F5-1F1F1","j":["flag","pl","nation","country","banner"]},"flag-st-pierre--miquelon":{"a":"Flag: St. Pierre & Miquelon","b":"1F1F5-1F1F2","j":["flag","flag_st_pierre_miquelon","saint","pierre","miquelon","nation","country","banner"]},"flag-pitcairn-islands":{"a":"Flag: Pitcairn Islands","b":"1F1F5-1F1F3","j":["flag","pitcairn","nation","country","banner"]},"flag-puerto-rico":{"a":"Flag: Puerto Rico","b":"1F1F5-1F1F7","j":["flag","puerto","rico","nation","country","banner"]},"flag-palestinian-territories":{"a":"Flag: Palestinian Territories","b":"1F1F5-1F1F8","j":["flag","palestine","palestinian","territories","nation","country","banner"]},"flag-portugal":{"a":"Flag: Portugal","b":"1F1F5-1F1F9","j":["flag","pt","nation","country","banner"]},"flag-palau":{"a":"Flag: Palau","b":"1F1F5-1F1FC","j":["flag","pw","nation","country","banner"]},"flag-paraguay":{"a":"Flag: Paraguay","b":"1F1F5-1F1FE","j":["flag","py","nation","country","banner"]},"flag-qatar":{"a":"Flag: Qatar","b":"1F1F6-1F1E6","j":["flag","qa","nation","country","banner"]},"flag-runion":{"a":"Flag: Réunion","b":"1F1F7-1F1EA","j":["flag","flag_reunion","réunion","nation","country","banner"]},"flag-romania":{"a":"Flag: Romania","b":"1F1F7-1F1F4","j":["flag","ro","nation","country","banner"]},"flag-serbia":{"a":"Flag: Serbia","b":"1F1F7-1F1F8","j":["flag","rs","nation","country","banner"]},"flag-russia":{"a":"Flag: Russia","b":"1F1F7-1F1FA","j":["flag","russian","federation","nation","country","banner"]},"flag-rwanda":{"a":"Flag: Rwanda","b":"1F1F7-1F1FC","j":["flag","rw","nation","country","banner"]},"flag-saudi-arabia":{"a":"Flag: Saudi Arabia","b":"1F1F8-1F1E6","j":["flag","nation","country","banner"]},"flag-solomon-islands":{"a":"Flag: Solomon Islands","b":"1F1F8-1F1E7","j":["flag","solomon","islands","nation","country","banner"]},"flag-seychelles":{"a":"Flag: Seychelles","b":"1F1F8-1F1E8","j":["flag","sc","nation","country","banner"]},"flag-sudan":{"a":"Flag: Sudan","b":"1F1F8-1F1E9","j":["flag","sd","nation","country","banner"]},"flag-sweden":{"a":"Flag: Sweden","b":"1F1F8-1F1EA","j":["flag","se","nation","country","banner"]},"flag-singapore":{"a":"Flag: Singapore","b":"1F1F8-1F1EC","j":["flag","sg","nation","country","banner"]},"flag-st-helena":{"a":"Flag: St. Helena","b":"1F1F8-1F1ED","j":["flag","saint","helena","ascension","tristan","cunha","nation","country","banner"]},"flag-slovenia":{"a":"Flag: Slovenia","b":"1F1F8-1F1EE","j":["flag","si","nation","country","banner"]},"flag-svalbard--jan-mayen":{"a":"Flag: Svalbard & Jan Mayen","b":"1F1F8-1F1EF","j":["flag","flag_svalbard_jan_mayen"]},"flag-slovakia":{"a":"Flag: Slovakia","b":"1F1F8-1F1F0","j":["flag","sk","nation","country","banner"]},"flag-sierra-leone":{"a":"Flag: Sierra Leone","b":"1F1F8-1F1F1","j":["flag","sierra","leone","nation","country","banner"]},"flag-san-marino":{"a":"Flag: San Marino","b":"1F1F8-1F1F2","j":["flag","san","marino","nation","country","banner"]},"flag-senegal":{"a":"Flag: Senegal","b":"1F1F8-1F1F3","j":["flag","sn","nation","country","banner"]},"flag-somalia":{"a":"Flag: Somalia","b":"1F1F8-1F1F4","j":["flag","so","nation","country","banner"]},"flag-suriname":{"a":"Flag: Suriname","b":"1F1F8-1F1F7","j":["flag","sr","nation","country","banner"]},"flag-south-sudan":{"a":"Flag: South Sudan","b":"1F1F8-1F1F8","j":["flag","south","sd","nation","country","banner"]},"flag-so-tom--prncipe":{"a":"Flag: São Tomé & Príncipe","b":"1F1F8-1F1F9","j":["flag","flag_sao_tome_principe","sao","tome","principe","nation","country","banner"]},"flag-el-salvador":{"a":"Flag: El Salvador","b":"1F1F8-1F1FB","j":["flag","el","salvador","nation","country","banner"]},"flag-sint-maarten":{"a":"Flag: Sint Maarten","b":"1F1F8-1F1FD","j":["flag","sint","maarten","dutch","nation","country","banner"]},"flag-syria":{"a":"Flag: Syria","b":"1F1F8-1F1FE","j":["flag","syrian","arab","republic","nation","country","banner"]},"flag-eswatini":{"a":"Flag: Eswatini","b":"1F1F8-1F1FF","j":["flag","sz","nation","country","banner"]},"flag-tristan-da-cunha":{"a":"Flag: Tristan Da Cunha","b":"1F1F9-1F1E6","j":["flag"]},"flag-turks--caicos-islands":{"a":"Flag: Turks & Caicos Islands","b":"1F1F9-1F1E8","j":["flag","flag_turks_caicos_islands","turks","caicos","islands","nation","country","banner"]},"flag-chad":{"a":"Flag: Chad","b":"1F1F9-1F1E9","j":["flag","td","nation","country","banner"]},"flag-french-southern-territories":{"a":"Flag: French Southern Territories","b":"1F1F9-1F1EB","j":["flag","french","southern","territories","nation","country","banner"]},"flag-togo":{"a":"Flag: Togo","b":"1F1F9-1F1EC","j":["flag","tg","nation","country","banner"]},"flag-thailand":{"a":"Flag: Thailand","b":"1F1F9-1F1ED","j":["flag","th","nation","country","banner"]},"flag-tajikistan":{"a":"Flag: Tajikistan","b":"1F1F9-1F1EF","j":["flag","tj","nation","country","banner"]},"flag-tokelau":{"a":"Flag: Tokelau","b":"1F1F9-1F1F0","j":["flag","tk","nation","country","banner"]},"flag-timorleste":{"a":"Flag: Timor-Leste","b":"1F1F9-1F1F1","j":["flag","flag_timor_leste","timor","leste","nation","country","banner"]},"flag-turkmenistan":{"a":"Flag: Turkmenistan","b":"1F1F9-1F1F2","j":["flag","nation","country","banner"]},"flag-tunisia":{"a":"Flag: Tunisia","b":"1F1F9-1F1F3","j":["flag","tn","nation","country","banner"]},"flag-tonga":{"a":"Flag: Tonga","b":"1F1F9-1F1F4","j":["flag","to","nation","country","banner"]},"flag-turkey":{"a":"Flag: Turkey","b":"1F1F9-1F1F7","j":["flag","turkey","nation","country","banner"]},"flag-trinidad--tobago":{"a":"Flag: Trinidad & Tobago","b":"1F1F9-1F1F9","j":["flag","flag_trinidad_tobago","trinidad","tobago","nation","country","banner"]},"flag-tuvalu":{"a":"Flag: Tuvalu","b":"1F1F9-1F1FB","j":["flag","nation","country","banner"]},"flag-taiwan":{"a":"Flag: Taiwan","b":"1F1F9-1F1FC","j":["flag","tw","nation","country","banner"]},"flag-tanzania":{"a":"Flag: Tanzania","b":"1F1F9-1F1FF","j":["flag","tanzania","united","republic","nation","country","banner"]},"flag-ukraine":{"a":"Flag: Ukraine","b":"1F1FA-1F1E6","j":["flag","ua","nation","country","banner"]},"flag-uganda":{"a":"Flag: Uganda","b":"1F1FA-1F1EC","j":["flag","ug","nation","country","banner"]},"flag-us-outlying-islands":{"a":"Flag: U.S. Outlying Islands","b":"1F1FA-1F1F2","j":["flag","flag_u_s_outlying_islands"]},"flag-united-nations":{"a":"Flag: United Nations","b":"1F1FA-1F1F3","j":["flag","un","banner"]},"flag-united-states":{"a":"Flag: United States","b":"1F1FA-1F1F8","j":["flag","united","states","america","nation","country","banner"]},"flag-uruguay":{"a":"Flag: Uruguay","b":"1F1FA-1F1FE","j":["flag","uy","nation","country","banner"]},"flag-uzbekistan":{"a":"Flag: Uzbekistan","b":"1F1FA-1F1FF","j":["flag","uz","nation","country","banner"]},"flag-vatican-city":{"a":"Flag: Vatican City","b":"1F1FB-1F1E6","j":["flag","vatican","city","nation","country","banner"]},"flag-st-vincent--grenadines":{"a":"Flag: St. Vincent & Grenadines","b":"1F1FB-1F1E8","j":["flag","flag_st_vincent_grenadines","saint","vincent","grenadines","nation","country","banner"]},"flag-venezuela":{"a":"Flag: Venezuela","b":"1F1FB-1F1EA","j":["flag","ve","bolivarian","republic","nation","country","banner"]},"flag-british-virgin-islands":{"a":"Flag: British Virgin Islands","b":"1F1FB-1F1EC","j":["flag","british","virgin","islands","bvi","nation","country","banner"]},"flag-us-virgin-islands":{"a":"Flag: U.S. Virgin Islands","b":"1F1FB-1F1EE","j":["flag","flag_u_s_virgin_islands","virgin","islands","us","nation","country","banner"]},"flag-vietnam":{"a":"Flag: Vietnam","b":"1F1FB-1F1F3","j":["flag","viet","nam","nation","country","banner"]},"flag-vanuatu":{"a":"Flag: Vanuatu","b":"1F1FB-1F1FA","j":["flag","vu","nation","country","banner"]},"flag-wallis--futuna":{"a":"Flag: Wallis & Futuna","b":"1F1FC-1F1EB","j":["flag","flag_wallis_futuna","wallis","futuna","nation","country","banner"]},"flag-samoa":{"a":"Flag: Samoa","b":"1F1FC-1F1F8","j":["flag","ws","nation","country","banner"]},"flag-kosovo":{"a":"Flag: Kosovo","b":"1F1FD-1F1F0","j":["flag","xk","nation","country","banner"]},"flag-yemen":{"a":"Flag: Yemen","b":"1F1FE-1F1EA","j":["flag","ye","nation","country","banner"]},"flag-mayotte":{"a":"Flag: Mayotte","b":"1F1FE-1F1F9","j":["flag","yt","nation","country","banner"]},"flag-south-africa":{"a":"Flag: South Africa","b":"1F1FF-1F1E6","j":["flag","south","africa","nation","country","banner"]},"flag-zambia":{"a":"Flag: Zambia","b":"1F1FF-1F1F2","j":["flag","zm","nation","country","banner"]},"flag-zimbabwe":{"a":"Flag: Zimbabwe","b":"1F1FF-1F1FC","j":["flag","zw","nation","country","banner"]},"flag-england":{"a":"Flag: England","b":"1F3F4-E0067-E0062-E0065-E006E-E0067-E007F","j":["flag","english"]},"flag-scotland":{"a":"Flag: Scotland","b":"1F3F4-E0067-E0062-E0073-E0063-E0074-E007F","j":["flag","scottish"]},"flag-wales":{"a":"Flag: Wales","b":"1F3F4-E0067-E0062-E0077-E006C-E0073-E007F","j":["flag","welsh"]}},"aliases":{}} \ No newline at end of file diff --git a/vector/src/main/res/values-ar/strings.xml b/vector/src/main/res/values-ar/strings.xml index aac9d1b6cd..4e74426e9a 100644 --- a/vector/src/main/res/values-ar/strings.xml +++ b/vector/src/main/res/values-ar/strings.xml @@ -1,17 +1,16 @@ - أرسلَ %1$s صُّورة. - دعوة مِن %s - %1$s دَعى %2$s - دعاكَ %1$s - إنضّم %1$s إلى الغُرفة - غادر %1$s الغرفة - رفضَ %1$s الدعوة - %1$s طردَ %2$s - إنَّ %1$s قد رفعَ الحظر عن %2$s - إنَّ %1$s قد حظرَ %2$s - إنَّ %1$s قد غيَّرَ صورته الشخصية - إنَّ %1$s قد عيَّنَ اسمه الظاهر إلى %2$s + أرسلَ ⁨%1$s⁩ صورةً. + دعوة من ⁨%s⁩ + دعا ⁨%1$s⁩ ⁨%2$s⁩ + دعاكَ ⁨%1$s⁩ + انضمّ ⁨%1$s⁩ إلى الغرفة + غادرَ ⁨%1$s⁩ الغرفة + رفضَ ⁨%1$s⁩ الدعوة + طردَ ⁨%1$s⁩ ⁨%2$s⁩ + رفعَ ⁨%1$s⁩ المنع عن ⁨%2$s⁩ + منعَ ⁨%1$s⁩ ⁨%2$s⁩ + غيّر ⁨%1$s⁩ صورته الشخصية إنَّ %1$s قد غيَّرَ اسمه الظاهر من %2$s إلى %3$s إنَّ %1$s قد أزالَ اسمه الظاهر (لقد كان %2$s) إنَّ %1$s قد غيَّرَ الموضوع إلى: %2$s @@ -42,12 +41,12 @@ عُنوان البريد الإلكتروني رقم الهاتف ‏‏⁨%1$s⁩: ‏⁨%2$s⁩ - إنَّ %1$s قد سحبَ دعوة %2$s + سحبَ ⁨%1$s⁩ الدعوة الموجّهة إلى ⁨%2$s إنَّ %s قد أجرى مُكالمة مرئية. إنَّ %s قد أجرى مُكالمة صوتية. إنَّ %1$s قد قَبَل دعوة %2$s يتعذَّر التنقيح - أرسلَ %1$s مُلصقًا. + أرسلَ ⁨%1$s⁩ ملصقًا. (تمَّ تغيِّير الصُّورة أيضًا) دَعوة مِن ⁨%s⁩ غُرفة فارِغة @@ -61,20 +60,20 @@ %1$s و%2$d آخرون %1$s و%2$d آخرون - أرسلتَ صُّورة. - أرسلتَ مُلصقًا. - دعوة مِنكَ - أنشأ %1$s الغُرفة - أنتَ أنشأتَ الغُرفة - أنتَ دعوتَ %1$s - أنتَ انضممت إلى الغُرفة - أنتَ غادرتَ الغُرفة + أرسلتَ صورةً. + أرسلتَ ملصقًا. + دعوة منك + أنشأ ⁨%1$s⁩ الغرفة + أنشأتَ الغرفة + دعوتَ ⁨%1$s⁩ + انضممتَ إلى الغرفة + غادرتَ الغرفة رفضتَ الدعوة - طردتَ %1$s - أنتَ قد رفعتَ الحظر عن %1$s - أنتَ قد حظرتَ %1$s - أنتَ قد سحبتَ دعوة %1$s - أنتَ قد غيّرتَ صورتك الشخصية + طردتَ ⁨%1$s⁩ + رفعتَ المنع عن ⁨%1$s⁩ + منعتَ ⁨%1$s⁩ + سحبتَ الدعوة الموجّهة إلى ⁨%1$s⁩ + غيّرتَ صورتك الشخصية أنتَ قد عيَّنتَ اسمك الظاهر إلى %1$s أنتَ قد غيّرتَ اسمك الظاهر من ⁨%1$s⁩ إلى ⁨%2$s⁩ أنتَ قد أزلتَ اسمك الظاهر (لقد كان ⁨%1$s⁩) @@ -140,12 +139,12 @@ إنَّ %s قد قامَ بالترقية هُنا. أنتَ قد جعلتَ الرسائل المُستقبلية مرئية لـ %1$s إنَّ %1$s قد جعلَ الرسائل المُستقبلية مرئية لـ %2$s - غادرت الغرفة - غادر ⁨%1$s⁩ الغرفة - أنت انضممت - انضم %1$s - أنتَ أنشأتَ المُناقشة - أنشأ %1$s المُناقشة + غادرتَ الغرفة + غادرَ ⁨%1$s⁩ الغرفة + انضممت + انضمّ ⁨%1$s⁩ + أنشأتَ النقاش + أنشأ ⁨%1$s⁩ النقاش أنتَ قد سحبتَ دعوة %1$s. السبب: %2$s إنَّ %1$s قد سحبَ دعوة %2$s. السبب: %3$s أنتَ قد ألغيتَ دعوة %1$s للإنضمام إلى الغُرفة. السبب: %2$s diff --git a/vector/src/main/res/values-ca/strings.xml b/vector/src/main/res/values-ca/strings.xml index 8e99a0a924..d5a9ed5b3e 100644 --- a/vector/src/main/res/values-ca/strings.xml +++ b/vector/src/main/res/values-ca/strings.xml @@ -880,7 +880,7 @@ \n \nSessions desconegudes: - Escolliu un directori de sale + Tria un directori de sales És possible que el servidor no estigui disponible o que estigui sobrecarregat Introdueix un servidor base per veure les seves sales públiques URL del servidor base @@ -1513,7 +1513,7 @@ Crea sala nova No hi ha xarxa. Si us plau comproveu la vostra connexió a internet. Canviar - Canviar de xarxa + Canvia de xarxa Espereu, si us plau… Totes les comunitats Aquesta sala no es pot pre-visualitzar @@ -1702,7 +1702,7 @@ Activa lliscar per respondre a la cronologia Cerca a partir del nom o l\'ID Nom o ID (#exemple:matrix.org) - Veu el directori de la sala + Mostra el directori de la sales Crea una nova sala No trobes el què busques\? No s\'han trobat edicions @@ -2145,7 +2145,7 @@ %d usuari vetat %d usuaris vetats - No s\'ha pogut obtenir la visibilitat actual del directori de sala (%1$s). + No s\'ha pogut obtenir la visibilitat actual del directori de sales (%1$s). Publicar aquesta sala al directori públic de sales %1$s\? Publica l\'adreça Anul·la la publicació de l\'adreça @@ -2729,4 +2729,15 @@ Elimina la icona Canvia la icona El servidor local accepta fitxers adjunts (fotos, fitxers, etc) de fins a una mida de %s. + Directori de sales + Mostra totes les sales al directori de sales, incloses aquelles amb contingut explícit. + Mostra sales amb contingut explícit + Estàs segur que vols eliminar tots els missatges no enviats d\'aquesta sala\? + Elimina missatges no enviats + Missatges amb enviament fallit + Vols aturar l\'enviament del missatge\? + Elimina tots els missatges fallits + Ha fallat + Enviat + Enviant \ No newline at end of file diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index 03b733be05..6d6574a87e 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -1,24 +1,24 @@ %1$s: %2$s - Uživatel %1$s poslal obrázek. - Uživatel %1$s poslal nálepku. + %1$s poslal(a) obrázek. + %1$s poslal(a) nálepku. Pozvání od uživatele %s - Uživatel %1$s pozval uživatele %2$s - Uživatel %1$s vás pozval - %1$s vstoupil do místnosti - Uživatel %1$s odešel + %1$s pozval(a) %2$s + %1$s vás pozval(a) + %1$s vstoupil(a) do místnosti + Uživatel %1$s odešel z místnosti %1$s odmítli pozvání %1$s vykopli %2$s %1$s zrušil vykázání %2$s %1$s vykázali %2$s %1$s zrušili pozvání pro %2$s - %1$s změnili svůj profilový obrázek + %1$s změnil(a) svůj profilový obrázek %1$s nastavili své veřejné jméno na %2$s - %1$s změnili své veřejné jméno z %2$s na %3$s + %1$s změnil(a) své veřejné jméno z %2$s na %3$s %1$s odstranili své veřejné jméno (%2$s) %1$s změnili téma na: %2$s - %1$s změnili název místnosti na: %2$s + %1$s změnil(a) název místnosti na: %2$s %s uskutečnili videohovor. %s uskutečnili hlasový hovor. %s přijali hovor. @@ -103,7 +103,7 @@ Změnili jste své veřejné jméno z %1$s na %2$s Odstranili jste své veřejné jméno (%1$s) Změnili jste téma na: %1$s - %1$s změnili obrázek místnosti + %1$s změnil(a) obrázek místnosti Změnili jste obrázek místnosti Změnili jste jméno místnosti na: %1$s Zahájili jste video hovor. @@ -136,7 +136,7 @@ Vlastní (%1$d) Vlastní Změnili jste %1$s stupeň oprávnění. - %1$s změnili %2$s stupeň oprávnění. + %1$s změnil(a) %2$s stupeň oprávnění. %1$s z %2$s na %3$s Pozvání od %1$s. Důvod: %2$s Vaše pozvání. Důvod: %1$s @@ -211,7 +211,7 @@ Učinili jste budoucí zprávy viditelné pro %1$s %1$s učinili budoucí zprávy viditelné pro %2$s Odešli jste z místnosti - %1$s odešli z místnosti + Uživatel %1$s odešel z místnosti Vstoupili jste %1$s vstoupili Založili jste diskusi @@ -233,7 +233,7 @@ • Servery shodující se s %s byly odstraněny ze seznamu zakázaných. • Servery shodující se s %s jsou nyní zakázány. Změnili jste ACL serveru pro tuto místnost. - %s změnili ACL serveru pro tuto místnost. + %s změnil(a) ACL serveru pro tuto místnost. • Server shodující se doslovně s IP je povolen. • Server shodující se doslovně s IP je zakázán. • Server shodující se s %s je povolen. @@ -241,11 +241,11 @@ Nastavili jste ACL serveru pro tuto místnost. %s nastavili ACL serveru pro tuto místnost. Změnili jste adresy pro tuto místnost. - %1$s změnili adresy pro tuto místnost. + %1$s změnil(a) adresy pro tuto místnost. Změnili jste hlavní a alternativní adresu pro tuto místnost. - %1$s změnili hlavní a alternativní adresu pro tuto místnost. + %1$s změnil(a) hlavní a alternativní adresu pro tuto místnost. Změnili jste alternativní adresu pro tuto místnost. - %1$s změnili alternativní adresu pro tuto místnost. + %1$s změnil(a) alternativní adresu pro tuto místnost. Odstranili jste alternativní adresu %1$s pro tuto místnost. Odstranili jste alternativní adresy %1$s pro tuto místnost. @@ -1984,7 +1984,7 @@ Důvěryhodné Nedůvěryhodné Tato relace je důvěryhodná pro bezpečnou komunikaci, protože %1$s (%2$s) ji ověřil: - %1$s (%2$s) se přihlásil skrze novou relaci: + %1$s (%2$s) se přihlásil novou relací: Dokud tento uživatel nezačne důvěřovat této relaci, zprávy z ní odeslané a v ní přijaté budou označeny varováním. Volitelně ji můžete manuálně ověřit. Spustit křížové podepsání Resetovat klíče @@ -2417,7 +2417,7 @@ Zahrnuje události pozvat/vstoupit/opustit/vykopnout/vykázat a změny avatara/veřejného jména. Průzkum Tlačítka botů - Reagovali skrze: %s + Reagoval(a): %s Výsledek ověření Odkaz byl chybně zformován K zahájení hovoru v této místnosti nemáte oprávnění @@ -2712,4 +2712,15 @@ Přepnout Úvodní synchronizace: \nStahuji data… + Opravdu chcete smazat všechny neodeslané zprávy v této místnosti\? + Smazat neodeslané zprávy + Zprávy se nepodařilo odeslat + Chcete zrušit odesílání zprávy\? + Smazat všechny zprávy, které se nepodařilo odeslat + Selhalo + Odesláno + Odesílá se + Zobrazit všechny místnosti v adresáři místností, včetně místností s explicitním obsahem. + Zobrazit místnosti s explicitním obsahem + Adresář místností \ No newline at end of file diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 437c517574..52e67eb460 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -161,7 +161,7 @@ %1$s hat das %2$s Widget modifiziert Du hast das %1$s Widget modifiziert Administrator - Moderator:in + Moderator Standard Benutzerdefiniert (%1$d) Benutzerdefiniert @@ -348,12 +348,12 @@ Keine Ergebnisse Räume - Raum-Verzeichnis + Raumverzeichnis Keine Räume Keine öffentl. Räume verfügbar - %d Benutzer/in - %d Benutzer/innen + %d Benutzer + %d Benutzer Logdateien übermitteln Absturzberichte übermitteln @@ -494,8 +494,8 @@ ${app_name} benötigt die Berechtigung, auf Kamera und Mikrofon zu zugreifen, um Video-Anrufe durchzuführen. \n \nBitte erlaube den Zugriff im nächsten Dialog, um den Anruf durchzuführen. - ${app_name} kann dein Adressbuch durchsuchen, um andere Matrix-Nutzer:innen anhand ihrer Email-Adresse und Telefonnummer zu finden. Wenn du der Nutzung deines Adressbuchs zu diesem Zweck zustimmst, erlaube den Zugriff im nächsten Popup-Fenster. - ${app_name} kann dein Adressbuch durchsuchen, um andere Matrix-Nutzer:innen anhand ihrer E-Mail-Adresse und Telefonnummer zu finden. + ${app_name} kann dein Adressbuch durchsuchen, um andere Matrix-Nutzer anhand ihrer Email-Adresse und Telefonnummer zu finden. Wenn du der Nutzung deines Adressbuchs zu diesem Zweck zustimmst, erlaube den Zugriff im nächsten Popup-Fenster. + ${app_name} kann dein Adressbuch durchsuchen, um andere Matrix-Nutzer anhand ihrer E-Mail-Adresse und Telefonnummer zu finden. \n \nStimmst du der Nutzung deines Adressbuchs zu diesem Zweck zu\? Entschuldige. Die Aktion wurde aufgrund fehlender Berechtigungen nicht ausgeführt @@ -540,22 +540,22 @@ Aus diesem Raum entfernen Verbannen Verbannung aufheben - Zum/r normalen Benutzer/in herabstufen - Zum/r Moderator:in machen + Zum normalen Benutzer herabstufen + Zum Moderator machen Zum Admin machen - Alle Nachrichten dieses/r Nutzers/in verbergen - Alle Nachrichten dieses/r Nutzers/in anzeigen + Alle Nachrichten dieses Nutzers verbergen + Alle Nachrichten dieses Nutzers anzeigen Nutzer-ID, Name oder E-Mail-Adresse Erwähnen Sitzungsliste anzeigen - Du wirst diese Änderung nicht rückgängig machen können, da der/die Benutzer!n dasselbe Berechtigungslevel wie du erhalten wirst. + Du wirst diese Änderung nicht rückgängig machen können, da der Benutzer dieselbe Berechtigungsstufe wie du erhalten wirst. \nBist du sicher\? "Bist du sicher, dass du %s in diesen Chat einladen willst?" Mit ID einladen LOKALE KONTAKTE (%d) Nur Matrix-Benutzer - Benutzer:in per ID einladen + Benutzer per ID einladen Bitte gib eine oder mehrere E-Mail-Adressen oder eine Matrix-ID ein E-Mail or Matrix-ID @@ -582,10 +582,10 @@ Fingerabdruck (%s): Konnte Identität des Remote-Servers nicht verifizieren. Dies kann bedeuten, dass jemand böswillig deinen Internetverkehr abfängt oder dass dein Telefon dem Zertifikat, dass der Remote-Server anbietet, nicht vertraut. - Wenn der/die Server-Administrator:in dir mitgeteilt hat, dass dies zu erwarten sei, stelle sicher, dass der Fingerabdruck unten mit dem von deinem/r Administrator:in bereitgestellten übereinstimmt. + Wenn der Server-Administrator dir mitgeteilt hat, dass dies zu erwarten sei, stelle sicher, dass der Fingerabdruck unten mit dem von deinem Administrator bereitgestellten übereinstimmt. Das Zertifikat unterscheidet sich von dem Zertifikat, dem dein Gerät ursprünglich vertraut hat. Dies ist SEHR UNGEWÖHNLICH. Es wird empfohlen, dass du dieses neue Zertifikat NICHT AKZEPTIERST. Das Zertifikat hat sich von einem ursprünglich vertrauenswürdigem Zertifikat in ein nicht vertrauenswürdiges Zertifikat geändert. Eventuell wurde das Zertifikat des Servers erneuert. Bitte erkundige dich beim Server-Administrator, welcher Fingerprint als vertrauenswürdig gilt. - Akzeptiere das Zertifikat nur dann, wenn der/die Server-Administrator:in einen Fingerprint veröffentlicht hat, der mit dem obigen übereinstimmt. + Akzeptiere das Zertifikat nur dann, wenn der Server-Administrator einen Fingerprint veröffentlicht hat, der mit dem obigen übereinstimmt. Raum-Details Personen @@ -596,7 +596,7 @@ TEILNEHMER Grund für das Melden dieses Inhalts - Möchtest du alle Nachrichten dieses/r Nutzers/in verbergen\? + Möchtest du alle Nachrichten dieses Nutzers verbergen\? \n \nBeachte: Diese Aktion wird die App neu starten und einige Zeit brauchen. Upload abbrechen @@ -822,7 +822,7 @@ Sperren Zulassen Sitzung verifizieren - Vergleiche die folgenden Zeichen mit den Einstellungen in der Sitzung des/der anderen Nutzer!n und bestätige: + Vergleiche die folgenden Zeichen mit den Einstellungen in der Sitzung des anderen Nutzers und bestätige: Falls sie nicht übereinstimmen, wurde die Kommunikation vielleicht kompromittiert. Ich bestätige, dass die Schlüssel übereinstimmen @@ -886,7 +886,7 @@ Schwarzes Design Synchronisiere… Auf Ereignisse lauschen - Nachrichten, die meinen Anzeigenamen enthalten + Nachrichten mit meinem Anzeigenamen Nachrichten, die meinen Benutzernamen enthalten Du hast die neue Sitzung \'%s\' hinzugefügt, die jetzt Verschlüsselungs-Schlüssel anfordert. Deine bislang nicht verifiziertes Sitzung \'%s\' fordert Verschlüsselungs-Schlüssel an. @@ -923,13 +923,13 @@ Sicher, dass du einen Sprachanruf starten möchtest\? Sicher, dass du einen Videoanruf starten möchtest\? Gruppenliste - Ein Bann führt zu einem Ausschluss eines/r Nutzers/in von diesem Raum und verhindert einen erneuten Beitritt. + Ein Bann führt zu einem Ausschluss eines Nutzers von diesem Raum und verhindert einen erneuten Beitritt. Alle Nachrichten (laut) Alle Nachrichten Nur Erwähnungen Stumm URL-Vorschau im Chat - Vibriere beim Erwähnen eines/r Nutzers/in + Vibriere beim Erwähnen eines Nutzers Benachrichtigungen Dieser Raum zeigt für keine Community Avatare an Neue Community-ID (z.B. +foo:matrix.org) @@ -951,14 +951,14 @@ Eingeladen Filter Gruppen-Mitglieder Filter Gruppen-Räume - Die Community-Administration hat keine lange Beschreibung für diese Community zur Verfügung gestellt. + Der Community-Administrator hat keine lange Beschreibung für diese Community zur Verfügung gestellt. Du wurdest von %2$s aus %1$s gekickt Du wurdest von %2$s aus %1$s verbannt Grund: %1$s Erneut beitreten Raum vergessen Zum Startbildschirm hinzufügen - Community Avatare + Community-Avatare Schütteln, um einen Fehler zu melden Aktionen Mitglieder auflisten @@ -1042,8 +1042,8 @@ \n \nDie Deaktivierung deines Konto wird standardmäßig keine deiner gesendeten Nachrichten löschen. Wenn du möchtest, dass auch deine Nachrichten gelöscht werden, wähle zusätzlich die Option unten. \n -\nDie Sichtbarkeit deiner Nachrichten ist ähnlich wie bei E-Mails: Wenn deine Nachrichten gelöscht werden, bedeutet dies, dass von dir verschickte Nachrichten nicht mit neuen oder unregistrierten Nutzer:innen geteilt werden. Aber registrierte Nutzer:innen, die bereits Zugang zu diesen Nachrichten haben, behalten weiterhin Zugriff auf ihre Kopie. - Bitte alle Nachrichten, die ich gesendet habe, löschen, wenn mein Account deaktiviert wird (Warnung: Unterhaltungen werden für zukünftige Nutzer:innen unvollständig erscheinen) +\nDie Sichtbarkeit deiner Nachrichten ist ähnlich wie bei E-Mails: Wenn deine Nachrichten gelöscht werden, bedeutet dies, dass von dir verschickte Nachrichten nicht mit neuen oder unregistrierten Nutzer geteilt werden. Aber registrierte Nutzer, die bereits Zugang zu diesen Nachrichten haben, behalten weiterhin Zugriff auf ihre Kopie. + Bitte alle Nachrichten, die ich gesendet habe, löschen, wenn mein Konto deaktiviert wird (Warnung: Unterhaltungen werden für zukünftige Nutzer unvollständig erscheinen) Um fortzufahren, bitte Passwort eingeben: Account deaktivieren Drittanbieter-Lizenzen @@ -1067,15 +1067,15 @@ Du bist aktuell kein Mitglied einer Community. Benutze die Enter-Taste der Tastatur zum Senden Zeigt Aktionen - Bannt Benutzer:in mit angegebener ID + Bannt Benutzer mit angegebener ID Hebt die Verbannung des Benutzers mit angegebener ID auf Bestimmt das Berechtigungslevel des Benutzers Setzt Berechtigungen des Benutzers zurück - Lädt Benutzer:in mit angegebener Kennung in den aktuellen Raum ein + Lädt Benutzer mit angegebener Kennung in den aktuellen Raum ein Tritt dem Raum mit angegebenen Alias bei Verlasse Raum Setzt das Raum-Thema - Kickt Benutzer:in mit angegebener ID + Kickt Benutzer mit angegebener ID Ändert deinen Anzeigenamen (De-)Aktiviert Markdown Um das Matrix-App-Management zu reparieren @@ -1121,10 +1121,10 @@ Ressourcen-Limit Kontaktiere Administrator kontaktiere deinen Service-Administrator - Dieser Home-Server hat eine seiner Ressourcen-Grenzen erreicht, sodass einige Nutzer:innen sich nicht anmelden können. + Dieser Home-Server hat eine seiner Ressourcen-Grenzen erreicht, sodass einige Nutzer sich nicht anmelden können. Dieser Home-Server hat einen seiner Ressourcen-Limits überschritten. - Dieser Home-Server hat seine Obergrenze an monatlich aktiven Nutzer:innen erreicht, sodass einige Nutzer:innen sich nicht anmelden können. - Dieser Home-Server hat seine Obergrenze an monatlich aktiven Nutzer:innen erreicht. + Dieser Home-Server hat seine Obergrenze an monatlich aktiven Nutzer erreicht, sodass einige Nutzer sich nicht anmelden können. + Dieser Home-Server hat seine Obergrenze an monatlich aktiven Nutzer erreicht. Bitte %s um dieses Limit anheben zu lassen. Bitte %s um diesen Dienst weiter zu nutzen. Fehler @@ -1151,7 +1151,7 @@ Grund Linkvorschau im Chat aktivieren, falls dein Home-Server diese Funktion unterstützt. Sende Schreibbenachrichtigungen - Lasse andere Benutzer:innen wissen, dass du tippst. + Lasse andere Benutzer wissen, dass du tippst. Markdown-Formatierung Formatiere Nachrichten mittels Markdown-Syntax, bevor sie gesendet werden. Dies erlaubt erweiterte Formatierungen, etwa Sternchen (*) um kursiven Text anzuzeigen. Zeige Lesebestätigungen @@ -1229,7 +1229,7 @@ Prüfung der Play-Dienste Google Play-Dienste-APK ist verfügbar und aktuell. Token-Registrierung - Wenn ein:e Benutzer:in ein abgestecktes Gerät mit ausgeschaltetem Bildschirm eine Weile nicht bewegt, wechselt es in den Bereitschaftsmodus. Dies hindert Apps daran, auf das Netzwerk zuzugreifen und verzögert die Ausführung von Aufgaben, Synchronisierungen und Standard-Alarmen. + Wenn ein Benutzer ein abgestecktes Gerät mit ausgeschaltetem Bildschirm eine Weile nicht bewegt, wechselt es in den Bereitschaftsmodus. Dies hindert Apps daran, auf das Netzwerk zuzugreifen und verzögert die Ausführung von Aufgaben, Synchronisierungen und Standard-Alarmen. Ignoriere Optimierungen Hintergrundverbindung ${app_name} muss eine Hintergrundverbindung (nur geringe Belastung) aufrechterhalten, um verlässliche Benachrichtigungen zu erhalten. @@ -1283,14 +1283,14 @@ Lösche Sicherung Präferenz der Benachrichtigungen nach Ereignis [%1$s] -\nDieser Fehler ist außerhalb von ${app_name} passiert. Google sagt, dass dieses Gerät zu viele Apps registriert hat um FCM zu nutzen. Der Fehler taucht nur auf, wenn sehr viele Apps installiert sind. Er sollte also den/die Durchschnittsnutzer:in nicht betreffen. +\nDieser Fehler ist außerhalb von ${app_name} passiert. Google sagt, dass dieses Gerät zu viele Apps registriert hat um FCM zu nutzen. Der Fehler taucht nur auf, wenn sehr viele Apps installiert sind. Er sollte also den Durchschnittsnutzer nicht betreffen. [%1$s] \nDieser Fehler liegt nicht unter der Kontrolle von ${app_name}. Er kann aus verschiedenen Gründen auftreten. Vielleicht wird es funktionieren, wenn du es später noch einmal probierst. Außerdem kannst Du prüfen, ob die Datennutzung der Google Play-Dienste unbeschränkt ist und die Geräteuhr richtig eingestellt ist. Der Fehler kann aber auch unter Custom-ROMs auftreten. [%1$s] \nDieser Fehler ist außerhalb von ${app_name} passiert. Es gibt kein Google-Konto auf dem Gerät. Bitte füge ein Google-Konto hinzu. Verwaltung der Krypto-Schlüssel Schlüssel-Sicherung verwalten - Nachrichten in verschlüsselten Räumen sind mit Ende-zu-Ende-Verschlüsselung gesichert. Nur du und der/die Empfänger!nnen haben die Schlüssel um diese Nachrichten zu lesen. + Nachrichten in verschlüsselten Räumen sind mit Ende-zu-Ende-Verschlüsselung gesichert. Nur du und der Empfänger haben die Schlüssel um diese Nachrichten zu lesen. \n \nSichere deine Schlüssel, um sie nicht zu verlieren. Der Wiederherstellungsschlüssel wurde nach \'%s\' gespeichert. @@ -1367,7 +1367,7 @@ Neue Schlüsselsicherung Eine neue Schlüsselsicherung wurde entdeckt. \n -\nWenn du die neue Wiederherstellungsmethode nicht festgelegt hast, kann ein/e Angreifer!n versuchen auf dein Konto zuzugreifen. Ändere dein Passwort und richte sofort eine neue Wiederherstellungsmethode in den Einstellungen ein. +\nWenn du die neue Wiederherstellungsmethode nicht festgelegt hast, kann ein Angreifer versuchen auf dein Konto zuzugreifen. Ändere dein Passwort und richte sofort eine neue Wiederherstellungsmethode in den Einstellungen ein. Ich war es Verliere nie mehr verschlüsselte Nachrichten Richte die Schlüsselsicherung ein @@ -1448,10 +1448,10 @@ Eingehende Verifizierungsanfrage Du hast eine eingehende Verifizierungsanfrage erhalten. Anfrage ansehen - Warte auf Bestätigung des/r anderen Nutzer*in… + Warte auf Bestätigung des Partners… Verifiziert! Du hast diese Sitzung erfolgreich verifiziert. - Sichere Nachrichten mit diesem/r Benutzer:in sind Ende-zu-Ende verschlüsselt und können nicht von Dritten mitgelesen werden. + Sichere Nachrichten mit diesem Benutzer sind Ende-zu-Ende verschlüsselt und können nicht von Dritten mitgelesen werden. Verstanden Schlüssel-Verifizierung Anfrage abgebrochen @@ -1460,7 +1460,7 @@ Interaktive Sitzungs-Verifizierung Verifizierungsanfrage %s möchte deine Sitzung verifizieren - Der/die Benutzer:in hat die Verifizierung abgebrochen + Der Benutzer hat die Verifizierung abgebrochen Der Verifizierungsprozess ist abgelaufen Die Sitzung hat eine unerwartete Nachricht erhalten Eine ungültige Nachricht wurde empfangen @@ -1483,7 +1483,7 @@ Reaktion hinzufügen Reaktionen ansehen Reaktionen - Ereignis von Benutzer:in gelöscht + Ereignis von Benutzer gelöscht Ereignis moderiert durch Raum-Administrator Zuletzt bearbeitet von %1$s am %2$s Neuen Raum erstellen @@ -1508,7 +1508,7 @@ Schlüsselaustausch anfragen Es sieht so aus, als hättest du bereits ein Setup-Schlüssel-Backup von einer anderen Sitzung. Möchtest du es durch das, was du gerade erstellt hast, ersetzen\? Für maximale Sicherheit empfehlen wir, dies persönlich zu tun, oder ein anderes vertrautes Kommunikationsmedium zu nutzen. - Überprüfe diese Sitzung, um sie als vertrauenswürdig zu markieren. Sitzungen von anderen Nutzer:innen zu vertrauen gibt dir zusätzliche Sicherheit bei der Verwendung von Ende-zu-Ende verschlüsselten Nachrichten. + Überprüfe diese Sitzung, um sie als vertrauenswürdig zu markieren. Sitzungen von anderen Nutzern zu vertrauen gibt dir zusätzliche Sicherheit bei der Verwendung von Ende-zu-Ende verschlüsselten Nachrichten. Durch Verifizieren dieser Sitzung wird sie bei dir und deinem Gegenüber als vertrauenswürdig markiert. Verifiziere diese Sitzung, indem du bestätigst, dass das folgende Emoji auf dem Bildschirm deines Gegenübers angezeigt wird Verifiziere diese Sitzung, indem du bestätigst, dass die folgenden Zahlen auf dem Bildschirm deines Gegenübers angezeigt werden @@ -1618,8 +1618,8 @@ ausstehend Gib einen neuen Identitätsserver ein Konnte keine Verbindung zum Home-Server herstellen - Bitte frage den/die Administrator/in deines Home-Servers (%1$s) nach der Einrichtung eines TURN-Servers, damit Anrufe zuverlässig funktionieren. -\n + Bitte frage den Administrator deines Home-Servers (%1$s) nach der Einrichtung eines TURN-Servers, damit Anrufe zuverlässig funktionieren. +\n \nAlternativ kann ein öffentlicher Server auf %2$s genutzt werden. Dies wird jedoch weniger zuverlässig sein und deine IP-Adresse wird gegenüber diesem Server preisgegeben. Du kannst den Server auch in den Einstellungen anpassen. Dies ist keine Adresse eines Matrixservers Kann Home-Server nicht bei dieser URL erreichen. Bitte überprüfen @@ -1668,7 +1668,7 @@ Auffindbare Telefonnummern Bitte gib die Adresse des Identitätsservers ein Identitätsserver hat keine Nutzungsbedingungen - Der Identitätsserver den du ausgewählt hast, hat keine Nutzungsbedingungen. Fahre nur fort, wenn du dem/r Besitzer!n des Dienstes vertraust + Der Identitätsserver den du ausgewählt hast, hat keine Nutzungsbedingungen. Fahre nur fort, wenn du dem Besitzer des Dienstes vertraust Eine Textnachricht wurde an %s gesendet. Bitte gib den Verifizierungscode ein, den sie enthält. Aktiviere ausführliche Logs. Ausführliche Logs werden der Entwicklung der App dadurch helfen, dass mehr Informationen übertragen werden, wenn du einen Fehlerbericht sendest. Auch wenn dies aktiviert ist, werden keine Nachrichteninhalte oder andere privaten Daten aufgezeichnet. @@ -1692,8 +1692,8 @@ gelesen von %1$s und %2$s gelesen von %s - gelesen von einem Nutzer:in - gelesen von %d Nutzer:innen + gelesen von einem Nutzer + gelesen von %d Nutzern Die Datei \'%1$s\' (%2$s) ist zu groß, um sie hochzuladen. Das Limit ist %3$s. Beim Abrufen des Anhangs ist ein Fehler aufgetreten. @@ -1709,24 +1709,24 @@ Diesen Inhalt melden Meldegrund MELDEN - NUTZER:IN IGNORIEREN + NUTZER IGNORIEREN Inhalt gemeldet - Dieser Inhalt wurde gemeldet. -\n -\nWenn du keine weiteren Inhalte dieses/r Nutzers/in sehen möchtest, kannst sie/ihn ignorieren, um jene Nachrichten auszublenden. + Dieser Inhalt wurde gemeldet. +\n +\nWenn du keine weiteren Inhalte dieses Nutzers sehen möchtest, kannst ihn ignorieren, um jene Nachrichten auszublenden. Als Spam gemeldet Dieser Inhalt wurde als Spam gemeldet. -\n -\nWenn du keine weiteren Inhalte dieses/r Nutzers/in sehen möchtest, kannst sie/ihn ignorieren, um jene Nachrichten auszublenden. +\n +\nWenn du keine weiteren Inhalte dieses Nutzers sehen möchtest, kannst ihn ignorieren, um jene Nachrichten auszublenden. Als unangebracht gemeldet Dieser Inhalt wurde als unangebracht gemeldet. -\n -\nWenn du keine weiteren Inhalte dieses/r Nutzers/in sehen möchtest, kannst sie/ihn ignorieren, um jene Nachrichten auszublenden. +\n +\nWenn du keine weiteren Inhalte dieses Nutzers sehen möchtest, kannst ihn ignorieren, um jene Nachrichten auszublenden. ${app_name} benötigt Berechtigungen, um deine E2E Schlüssel zu speichern. \n \nBitte erlaube den Zugriff im nächsten Pop-Up sodass du deine Schlüssel manuell exportieren kannst. Aktuell besteht keine Netzwerkverbindung - Nutzer:in ignorieren + Nutzer ignorieren Alle Nachrichten (laut) Alle Nachrichten Nur Erwähnungen @@ -1735,7 +1735,7 @@ Raum verlassen %1$s hat keine Änderungen gemacht Sendet die Nachricht als Spoiler - Du ignorierst keine Nutzer:innen + Du ignorierst keine Nutzer Halte auf einem Raum um mehr Optionen anzuzeigen %1$s hat den Raum für jeden, der den Link hat, öffentlich gemacht. Ungelesene Nachrichten @@ -1750,7 +1750,7 @@ Andere Benutzerdefinierte & erweiterte Einstellungen Fortfahren - Eine Trennung von deinem Identitätsserver würde bedeuten, dass du weder von anderen Nutzer:innen gefunden werden, noch diese per E-Mail oder Telefonnummer einladen kannst. + Eine Trennung von deinem Identitätsserver würde bedeuten, dass du weder von anderen Nutzern gefunden werden, noch diese per E-Mail oder Telefonnummer einladen kannst. Du teilst deine Email Adressen oder Telefonnummern momentan auf dem Identitätsserver %1$s. Du wirst dich erneut mit %2$s verbinden müssen, um mit dem Teilen aufzuhören. Stimme den Nutzungsbedingungen des Identitätsservers (%s) zu, um zu erlauben per E-Mail oder Telefonnummer gefunden zu werden. Zu teilende Daten nicht verarbeitbar @@ -1837,7 +1837,7 @@ Warnung Bitte löse das Captcha Veralteter Home-Server - Auf diesem Home-Server läuft eine zu alte Version, um eine Verbindung herzustellen. Bitten deine Home-Server-Administration um ein Upgrade. + Auf diesem Home-Server läuft eine zu alte Version, um eine Verbindung herzustellen. Bitte deinen Home-Server-Administrator um ein Upgrade. Es wurden zu viele Anfragen gesendet. Versuche es erneut in %1$d Sekunde… Es wurden zu viele Anfragen gesendet. Versuche es erneut in %1$d Sekunden… @@ -1850,11 +1850,11 @@ \n \n• Du hast diese Sitzung aus einer anderen Sitzung heraus gelöscht. \n -\n• Die Administration deines Servers hat deinen Zugriff aus Sicherheitsgründen ungültig gemacht. +\n• Der Administrator deines Servers hat deinen Zugriff aus Sicherheitsgründen ungültig gemacht. Melde dich erneut an Du bist abgemeldet Anmelden - Deine Home-Server-Administration (%1$s) hat dich von deinem Konto %2$s (%3$s) abgemeldet. + Dein Home-Server-Administrator (%1$s) hat dich von deinem Konto %2$s (%3$s) abgemeldet. Melden dich an, um ausschließlich auf diesem Gerät gespeicherte Verschlüsselungsschlüssel wiederherzustellen. Du benötigst sie, um deine verschlüsselten Nachrichten auf jedem Gerät zu lesen. Anmelden Passwort @@ -1868,7 +1868,7 @@ \nMelde dich erneut an, um auf deine Kontodaten und Nachrichten zuzugreifen. Du verlierst den Zugriff auf verschlüsselte Nachrichten, außer, du meldest dich an, um den Verschlüsselungsschlüssel wiederherzustellen. Daten löschen - Die aktuelle Sitzung gehört dem/der Benutzer!n%1$s. Die angegebenen Anmeldeinformationen sind von Benutzer!n %2$s. Dies wird nicht von ${app_name} unterstützt. + Die aktuelle Sitzung gehört dem Benutzer %1$s. Die angegebenen Anmeldeinformationen sind vom Benutzer %2$s. Dies wird nicht von ${app_name} unterstützt. \nBitte zuerst die Daten löschen und dann erneut anmelden. matrix.to-Link fehlerhaft Die Beschreibung ist zu kurz @@ -1876,7 +1876,7 @@ Alle meine Sitzungen anzeigen Erweiterte Einstellungen Entwicklungsmodus - Der Entwicklungsmodus aktiviert versteckte Funktionen und kann die Anwendung weniger stabil machen. Nur für Entwickler!nnen! + Der Entwicklungsmodus aktiviert versteckte Funktionen und kann die Anwendung weniger stabil machen. Nur für Entwickler! Wutschütteln Erkennungsschwelle Schüttel dein Telefon, um die Erkennungsschwelle zu testen @@ -1894,9 +1894,9 @@ Nicht vertrauenswürdige Anmeldung Sie stimmen überein Sie stimmen nicht überein - Verifiziere diese/n Benutzer!n, indem du bestätigst, dass diese einzigartigen Emoji in derselben Reihenfolge auf dem Bildschirm deines Gegenübers angezeigt werden. + Verifiziere diesen Benutzer, indem du bestätigst, dass diese einzigartigen Emoji in derselben Reihenfolge auf dem Bildschirm deines Gegenübers angezeigt werden. Für ultimative Sicherheit verwende ein anderes vertrauenswürdiges Kommunikationsmittel oder mache es persönlich. - Suche nach dem grünen Schild, um sicherzustellen, dass ein/e Benutzer!n vertrauenswürdig ist. Vertraue allen Benutzer!nnen in einem Raum, um sicherzustellen, dass der Raum sicher ist. + Suche nach dem grünen Schild, um sicherzustellen, dass ein Benutzer vertrauenswürdig ist. Vertraue alle Benutzer in einem Raum, um sicherzustellen, dass der Raum sicher ist. Nicht sicher Eine der folgenden Möglichkeiten kann beeinträchtigt sein: \n @@ -1919,7 +1919,7 @@ Manuelle Verifizierung Ich Scanne den Code mit dem Gerät des Gegenüber für eine gegenseitige Überprüfung - Scanne ihren/seinen Code + Scanne Code des Anderen Kann nicht scannen Wenn ihr nicht am selben Ort seid, vergleicht Emoji stattdessen Verifizieren via Emoji-Vergleich @@ -1952,7 +1952,7 @@ Moderierende benutzerdefiniert Eingeladen - Nutzer:innen + Nutzer Admin in %1$s Moderation in %1$s Springen & als gelesen markieren @@ -1978,7 +1978,7 @@ Vergleiche die einzigartigen Emoji und stell sicher, dass sie in derselben Reihenfolge angezeigt werden. Vergleiche den Code mit dem Code auf dem Bildschirm deines Gegenübers. Nachrichten mit diesem Gegenüber sind Ende-zu-Ende verschlüsselt und können nicht von Dritten gelesen werden. - Deine neue Sitzung ist jetzt verifiziert. Sie hat Zugriff auf deine verschlüsselten Nachrichten, und andere Benutzer!nnen sehen sie als vertrauenswürdig an. + Deine neue Sitzung ist jetzt verifiziert. Sie hat Zugriff auf deine verschlüsselten Nachrichten, und andere Benutzer sehen sie als vertrauenswürdig an. Cross-Signing Cross-Signing ist aktiviert \nPrivate Schlüssel auf dem Gerät. @@ -2000,7 +2000,7 @@ %d aktive Sitzungen Verifiziere diese Sitzung - Andere Benutzer!nnen vertrauen ihr möglicherweise nicht + Andere Benutzer vertrauen ihr möglicherweise nicht Vollständige Sicherheit Nutze eine vorhandene Sitzung um diese Sitzung zu verifizieren und ihr Zugriff auf verschlüsselte Nachrichten zu gewähren. Verifizieren @@ -2012,7 +2012,7 @@ Nicht vertraut Diese Sitzung ist für sichere Nachrichtenübertragung vertrauenswürdig, weil %1$s (%2$s) sie verifiziert hat: %1$s (%2$s) hat sich in einer neuen Sitzung angemeldet: - Bis diese/r Benutzer!n dieser Sitzung vertraut, werden an und von ihr/ihm gesendete Nachrichten mit Warnungen gekennzeichnet. Alternativ kannst du dies manuell überprüfen. + Bis dieser Benutzer dieser Sitzung vertraut, werden an und von ihm gesendete Nachrichten mit Warnungen gekennzeichnet. Alternativ kannst du dies manuell überprüfen. Initialisiere Cross-Signing Schlüssel zurücksetzen QR-Code @@ -2049,8 +2049,8 @@ Möchtest du dieses Ereignis wirklich entfernen (löschen)\? Beachte, dass beim Löschen eines Raumnamens oder einer Themenänderung die Änderung rückgängig gemacht werden kann. Grund hinzufügen Grund für das Editieren - Ereignis gelöscht von Benutzer!n, Grund: %1$s - Ereignis vom Raumadministration moderiert, Grund: %1$s + Ereignis durch den Benutzer gelöscht, Grund: %1$s + Ereignis vom Raumadministrator moderiert, Grund: %1$s Schlüssel sind bereits aktuell! Spoiler Benutzerdefiniert (%1$d) in %2$s @@ -2063,8 +2063,8 @@ Benutze diese Sitzung um deine neue zu verfizieren, damit sie auf verschlüsselte Nachrichten zugreifen kann. Das war ich nicht Dein Account ist möglicherweise komprimittiert - Wenn du abbrichst, wirst du auf diesem Gerät keine verschlüsselten Nachrichten lesen können, und andere Benutzer:innen werden ihm nicht vertrauen - Wenn du abbrichst, wirst du auf deinem neuen Gerät keine verschlüsselten Nachrichten lesen können, und andere Benutzer:innen werden ihm nicht vertrauen + Wenn du abbrichst, wirst du auf diesem Gerät keine verschlüsselten Nachrichten lesen können, und andere Benutzer werden ihm nicht vertrauen + Wenn du abbrichst, wirst du auf deinem neuen Gerät keine verschlüsselten Nachrichten lesen können, und andere Benutzer werden ihm nicht vertrauen Du wirst %1$s (%2$s) nicht verifizieren, wenn du jetzt abbrichst. Beginne in deren Nutzerprofil erneut. Eines der folgenden könnte kom­pro­mit­tie­rt sein: \n @@ -2115,7 +2115,7 @@ Dies kann nicht von einem mobilen Gerät erfolgen Wenn Räume verbessert werden Verschlüsselung aktiviert - Nachrichten in diesem Raum sind Ende-zu-Ende verschlüsselt. Erfahre mehr & verifiziere Benutzer:innen in deren Profil. + Nachrichten in diesem Raum sind Ende-zu-Ende verschlüsselt. Erfahre mehr & verifiziere Benutzer in deren Profil. Die Verschlüsselung in diesem Raum wird nicht unterstützt Warte auf %s… %s setzen @@ -2185,20 +2185,20 @@ Dieser Link %1$s bringt dich zu einer anderen Seite: %2$s. \n \nWillst du wirklich fortfahren\? - Konnte Direktnachricht nicht erzeugen. Prüfe die Nutzer:in, die du einladen willst und versuche es erneut. + Konnte Direktnachricht nicht erzeugen. Prüfe die Nutzer, die du einladen willst und versuche es erneut. %1$s: %2$s %1$s: %2$s %3$s - Nutzer!n hinzufügen + Mitglieder hinzufügen EINLADEN - Benutzer:innen werden eingeladen… - Benutzer:innen einladen + Benutzer werden eingeladen… + Benutzer einladen Einladung gesendet an %1$s Einladungen gesendet an %1$s und %2$s Einladungen gesendet an %1$s und einen weiteren Benutzer Einladungen gesendet an %1$s und %2$d weitere Benutzer - Wir konnten die Benutzer:innen nicht einladen. Bitte überprüfe die Benutzer:innen, welchen du einladen möchtest, und versuche es erneut. + Wir konnten den Benutzer nicht einladen. Bitte überprüfe den Benutzernamen, welchen du einladen möchtest und versuche es erneut. Pause Kopieren Benachrichtigungen @@ -2207,7 +2207,7 @@ Ablehnen Erfolg Echtzeitverbindung konnte nicht hergestellt werden. -\nBitte den/die Administrator/in deines Home-Servers, einen TURN-Server so zu konfigurieren, dass Anrufe zuverlässig funktionieren. +\nBitte den Administrator deines Home-Servers, einen TURN-Server so zu konfigurieren, dass Anrufe zuverlässig funktionieren. Wähle Audiogerät aus Telefonie Lautsprecher @@ -2224,25 +2224,25 @@ Zum Anruf zurückkehren Einladung zurückziehen Möchtest du dich zurückstufen\? - Du kannst die Zurückstufung nicht rückgängig machen und du wirst die Rechte nur mit einem/r anderen berechtigten Benutzer:in im Raum zurückerlangen können. + Du kannst die Zurückstufung nicht rückgängig machen und du wirst die Rechte nur mit einem anderen berechtigten Benutzer im Raum zurückerlangen können. Zurückstufen - Benutzer:in ignorieren - Durch das Ignorieren werden für dich alle Nachrichten des/r Nutzers/in ausgeblendet. + Benutzer ignorieren + Durch das Ignorieren werden für dich alle Nachrichten des Nutzers ausgeblendet. \n \nDu kannst die Aktion jederzeit in den allgemeinen Einstellungen rückgängig machen. Ignorieren des Benutzers rückgängig machen Das Aufheben der Ignorierung wird alle Nachrichten des Benutzers wieder einblenden. Einladung zurückziehen - Bist du dir sicher, dass du die Einladung für diese:n Benutzer:in zurückziehen möchtest\? - Benutzer:in entfernen + Bist du dir sicher, dass du die Einladung für diesen Benutzer zurückziehen möchtest\? + Benutzer entfernen Grund für das Entfernen - Das Entfernen wird den/die Benutzer!n von diesem Raum ausschließen. + Das Entfernen wird den Benutzer von diesem Raum ausschließen. \n -\nUm einen erneuten Beitritt zu verhindern, solltest du ihn/sie stattdessen bannen. - Benutzer:in bannen +\nUm einen erneuten Beitritt zu verhindern, solltest du ihn stattdessen bannen. + Benutzer bannen Grund für den Bann Bann des Benutzers aufheben - Das Aufheben des Bannes wird dem/r Benutzer:in erlauben dem Raum wieder beizutreten. + Das Aufheben des Bannes wird dem Benutzer erlauben dem Raum wieder beizutreten. Sicheres Backup Verwalten Backup einrichten @@ -2292,7 +2292,7 @@ Sticker Administrative Aktionen Standard in %1$s - Dein:e Serveradministrator:in hat in privaten Räumen & Direktnachrichten Ende-zu-Ende Verschlüsselung standardmäßig deaktiviert. + Dein Serveradministrator hat in privaten Räumen und Direktnachrichten Ende-zu-Ende Verschlüsselung standardmäßig deaktiviert. Flugzeugmodus ist aktiv Gib eine Sicherheitsphrase ein, die nur du kennst. Diese wird benutzt um deine Daten auf dem Server geheim zu halten. Wenn du jetzt abbrichst und den Zugriff zu deinen Sitzungen verlierst, kannst du verschlüsselte Nachrichten & Daten verlieren. @@ -2447,7 +2447,7 @@ Die Applikation wartet auf den PUSH Push testen Die Suche in verschlüsselten Räumen wird noch nicht unterstützt. - Gebannte Nutzer:innen filtern + Gebannte Nutzer filtern Du bist nicht berechtigt einen Anruf zu starten Du hast keine Berechtigung ein Konferenzgespräch zu starten Zeige Details wie Raumnamen und Nachrichteninhalt. @@ -2462,7 +2462,7 @@ Zeigen das Gerät, mit dem du jetzt überprüfen kannst Zeigen %d Geräte, mit denen du jetzt überprüfen kannst - Du wirst ohne Nachrichtenverlauf, Nachrichten, vertraute Geräten und vertraute Nutzer!nnen neu starten + Du wirst ohne Nachrichtenverlauf, Nachrichten, vertraute Geräten und vertraute Nutzern neu starten Wenn du alles zurücksetzt Mache dies nur, wenn du kein anderes Gerät hast, mit dem du dieses verifizieren kannst. Alles zurücksetzen @@ -2474,9 +2474,9 @@ Einstellungen Nachrichten hier sind Ende-zu-Ende verschlüsselt. \n -\nDeine Nachrichten sind mit digitalen Schlüsseln gesichert. Nur du und der/die Empfänger!n haben die einzigen Schlüssel, um jene zu entsperren. +\nDeine Nachrichten sind mit digitalen Schlüsseln gesichert. Nur du und der Empfänger haben die einzigen Schlüssel, um jene zu entsperren. Nachrichten hier sind nicht Ende-zu-Ende verschlüsselt. - Dieser Homeserver läuft mit einer alten Version. Bitte deine Homeserver-Administration um ein Upgrade. Du kannst fortfahren, aber einige Funktionen funktionieren möglicherweise nicht richtig. + Dieser Homeserver läuft mit einer alten Version. Bitte deinen Homeserver-Administrator um eine Aktualisierung. Du kannst fortfahren, aber einige Funktionen funktionieren möglicherweise nicht richtig. Du hast dies auf Einladungen beschränkt. %1$s hat dies auf Einladungen beschränkt. Zeige vollständigen Verlauf in verschlüsselten Räumen an @@ -2519,7 +2519,7 @@ E-Mails und Telefonnummern senden Vorschläge Kontakte - Bekannte Nutzer:innen + Bekannte Nutzer Kürzlich QR-Code Hinzufügen via QR-Code @@ -2586,7 +2586,7 @@ Diese Adresse veröffentlichen Lokale Adresse hinzufügen Dieser Raum hat keine lokalen Adressen - Füge Adressen für diesen Raum hinzu, damit andere Nutzer:innen ihn auf %1$s finden können + Füge Adressen für diesen Raum hinzu, damit andere Nutzer ihn auf %1$s finden können Lokale Adresse Neue öffentliche Adresse (z.B. #alias:server) Noch keine weiteren öffentlichen Adressen vorhanden. @@ -2603,12 +2603,12 @@ Haupt-Adresse des Raums ändern Raum-Bild ändern Widgets verändern - Jede/n benachrichtigen + Jeden benachrichtigen Von anderen gesendete Nachrichten entfernen - Nutzer:in verbannen - Nutzer:in entfernen + Nutzer verbannen + Nutzer entfernen Einstellungen ändern - Nutzer:in einladen + Nutzer einladen Nachrichten senden Standard Rolle Berechtigungen @@ -2641,7 +2641,7 @@ Erneute Authentifizierung erforderlich Cross Signing konnte nicht eingerichtet werden Nicht autorisierte, fehlende gültige Authentifizierungsdaten - Nutzer:innen + Nutzer Beim Übertragen des Anrufs ist ein Fehler aufgetreten Übertragen Verbinden @@ -2722,4 +2722,16 @@ \nLade Daten herunter… Erste Synchronisation: \nWarte auf Serverantwort… + Gesendet + Raumverzeichnis + Wechseln + Zeige alle Räume im Raumverzeichnis, inklusive der Räume mit anstößigen Inhalten. + Zeige Räume mit anstößigen Inhalten + Bist du dir sicher, dass du alle nicht gesendete Nachrichten in diesem Raum löschen willst\? + Nicht gesendete Nachrichten löschen + Fehlgeschlagen + Willst du zu sendende Nachrichten zurückziehen\? + Alle fehgeschlagene Nachrichten löschen + Senden der Nachricht gescheitert + Wird gesendet \ No newline at end of file diff --git a/vector/src/main/res/values-eo/strings.xml b/vector/src/main/res/values-eo/strings.xml index 747511f745..4e506e1d59 100644 --- a/vector/src/main/res/values-eo/strings.xml +++ b/vector/src/main/res/values-eo/strings.xml @@ -1596,7 +1596,7 @@ Por ligi al ĉambro, ĝi devas havi adreson. Nur anoj (ekde aliĝo) Nur anoj (ekde sia aliĝo) - Nur anoj (ekde elekto de ĉi tiu elekteblo) + Nur anoj (ekde ĉi tiu elekto) Ĉiu ajn Kio povas aliri ĉi tiun ĉambron\? Kiu povas legi historion\? @@ -2393,4 +2393,92 @@ Aldoni Ekbabili Implicita de sistemo + Ĉu forlasi la nunan grupan vokon kaj iri al la alia\? + Versio de ĉambro + Ne povis akiri la nunan videblecon en la katalogo de ĉambroj (%1$s). + Ĉu publikigi ĉi tiun ĉambron per la katalogo de ĉambroj de %1$s\? + Malpublikigi ĉi tiun adreson + Publikigi ĉi tiun adreson + Aldoni lokan adreson + Ĉi tiu ĉambro ne havas lokajn adresojn + Agordu adresojn por ĉi tiu ĉambro, por ke uzantoj ĝin facile trovu per via hejmservilo (%1$s) + Ŝanĝi la temon + Gradaltigi la ĉambron + Sendi eventojn de la speco «m.room.server_acl» + Ŝanĝi permesojn + Ŝanĝi nomon de ĉambro + Ŝanĝi videblecon de historio + Ŝalti tutvojan ĉifradon + Ŝanĝi ĉefadreson de la ĉambro + Ŝanĝi bildon de ĉambro + Ŝanĝi fenestraĵojn + Sciigi ĉiujn + Forigi mesaĝojn senditajn de aliuloj + Forbari uzantojn + Forpeli uzantojn + Ŝanĝi agordojn + Inviti uzantojn + Sendi mesaĝon + Ordinara rolo + Permesoj en ĉambro + Nerajtigite, mankas validaj aŭtentikigiloj + Montri ĉiujn ĉambrojn en la katalogo de ĉambro, inkluzive tiujn kun konsterna enhavo. + Montri ĉambrojn kun konsterna enhavo + Katalogo de ĉambroj + Nova valoro + Vi ŝanĝis la adresojn por ĉi tiu ĉambro. + %1$s ŝanĝis la adresojn por ĉi tiu ĉambro. + Vi ŝanĝis la ĉefan kaj alternativajn adresojn por ĉi tiu ĉambro. + %1$s ŝanĝis la ĉefan kaj alternativajn adresojn por ĉi tiu ĉambro. + Vi ŝanĝis la alternativajn adresojn por ĉi tiu ĉambro. + %1$s ŝanĝis la alternativajn adresojn por ĉi tiu ĉambro. + + Vi forigis la alternativan adreson %1$s por ĉi tiu ĉambro. + Vi forigis la alternativajn adresojn %1$s por ĉi tiu ĉambro. + + + %1$s forigis la alternativan adreson %2$s por ĉi tiu ĉambro. + %1$s forigis la alternativajn adresojn %2$s por ĉi tiu ĉambro. + + + Vi aldonis la alternativan adreson %1$s por ĉi tiu ĉambro. + Vi aldonis la alternativajn adresojn %1$s por ĉi tiu ĉambro. + + + %1$s aldonis la alternativan adreson %2$s por ĉi tiu ĉambro. + %1$s aldonis la alternativajn adresojn %2$s por ĉi tiu ĉambro. + + Komenca spegulado: +\nElŝutante datumojn… + Komenca spegulado: +\nAtendante respondon de servilo… + Malplena ĉambro (estis %s) + + %1$s, %2$s, %3$s, kaj %4$d alia + %1$s, %2$s, %3$s, kaj %4$d aliaj + + %1$s, %2$s, %3$s kaj %4$s + %1$s, %2$s kaj %3$s + Vi ŝanĝis grupan vidvokon + Grupan vidvokon ŝanĝis %1$s + Vi finis grupan vidvokon + Grupan vidvokon finis %1$s + Vi komencis grupan vidvokon + Grupan vidvokon komencis %1$s + 🎉 Partoprenado de ĉiuj serviloj estas malpermesita! Ĉi tiu ĉambro ne plu uzeblas. + Senŝanĝe. + • Serviloj akordaj kun precizaj IP-adresoj nun estas forbaritaj. + • Serviloj akordaj kun precizaj IP-adresoj nun estas permesitaj. + • Serviloj akordaj kun %s foriĝis de la listo de forbaritaj. + • Serviloj akordaj kun %s foriĝis de la listo de permesitaj. + • Serviloj akordaj kun %s nun estas permesitaj. + • Serviloj akordaj kun %s nun estas forbaritaj. + Vi ŝanĝis la alirpermesojn por serviloj por ĉi tiu ĉambro. + %s ŝanĝis la alirpermesojn por serviloj por ĉi tiu ĉambro. + • Serviloj akordaj kun precizaj IP-adresoj estas forbaritaj. + • Serviloj akordaj kun precizaj IP-adresoj estas permesitaj. + • Serviloj akordaj kun %s estas permesitaj. + • Serviloj akordaj kun %s estas forbaritaj. + Vi agordis la alirpermesojn por serviloj por ĉi tiu ĉambro. + %s agordis la alirpermesojn por serviloj por ĉi tiu ĉambro. \ No newline at end of file diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml index 2dd13ede4a..2b4a0e0590 100644 --- a/vector/src/main/res/values-et/strings.xml +++ b/vector/src/main/res/values-et/strings.xml @@ -2556,7 +2556,7 @@ Muuda vestlusajaloo nähtavust Võta jututoas kasutusele krüptimine Muuda jututoa põhiaadressi - Muuda jututoa profiilipilti ehk avatari + Muuda jututoa tunnuspilti ehk avatari Muuda vidinaid Teavita kõiki Kustuta teiste saadetud sõnumid @@ -2598,7 +2598,7 @@ See kõne on lõppenud %1$s keeldus kõnest Sa keeldusid %1$s kõnest - Sul on parasjagu see mõne pooleli + Sul on parasjagu see kõne pooleli %1$s alustas kõnet Sa alustasid kõnet Sina panid kõne ootele @@ -2661,4 +2661,15 @@ \nLaadin andmed alla… Esmane sünkroniseerimine: \nOotan serveri vastust… + Näita jututubade kataloogist kõiki jututubasid, sealhulgas neid, kus on ebasobilikku sisu. + Näita jututubasid, kus on ebasobilikku sisu + Nende sõnumite saatmine ei õnnestunud + Kas sa kindlasti soovid sellest jututoast kustutada kõik saatmata sõnumid\? + Kustuta saatmata sõnumid + Kas sa soovid katkestada sõnumi saatmist\? + Kustuta kõik sõnumid, mille saatmine ei õnnestunud + Saatmine ei õnnestunud + Saadetud + Saadan + Jututubade kataloog \ No newline at end of file diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index 0c7ab7a71c..7937f4f4b2 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -2661,4 +2661,15 @@ تطبیق سرور %s اجازه داده شده‌است. تطبیق سرور %s ممنوع شده‌است. شما ACL های سرور را برای این اتاق تنظیم کردید. + آیا مطمئن هستید که می خواهید همه پیام های ارسال نشده در این اتاق را حذف کنید؟ + حذف پیام‌های ارسال نشده + پیام ارسال نشد + آیا می خواهید ارسال پیام را لغو کنید؟ + حذف تمامی پیام‌های ناموفق + ناموفق + ارسال شد + در حال ارسال + نمایش همه‌ی اتاق‌های داخل فهرست. + نمایش‌ها همه‌ی اتاق‌ها + فهرست اتاق‌ها \ No newline at end of file diff --git a/vector/src/main/res/values-fi/strings.xml b/vector/src/main/res/values-fi/strings.xml index 1661178b50..33f19760af 100644 --- a/vector/src/main/res/values-fi/strings.xml +++ b/vector/src/main/res/values-fi/strings.xml @@ -102,7 +102,7 @@ %1$s poisti tältä huoneelta osoitteen %2$s. - %1$s poisti tältä huoneelta osoitteet %3$s. + %1$s poisti tältä huoneelta osoitteet %2$s. %1$s lisäsi tälle huoneelle osoitteen %2$s ja poisti osoitteen %3$s. %1$s asetti tämän huoneen pääosoitteeksi %2$s. @@ -2096,7 +2096,7 @@ Näytä emoji-näppäimistö Tiliisi ei ole lisätty sähköpostiosoitetta Tiliisi ei ole lisätty puhelinnumeroa - Ilmoittaa kaikille + Ilmoita kaikille Haluatko peruuttaa tämän käyttäjän kutsun\? Tämä huone ei ole julkinen. Jos poistut, et voi liittyä takaisin ilman kutsua. Tämän asetuksen käyttöön ottaminen lisää FLAG_SECURE kaikkiin toimintoihin. Käynnistä sovellus uudestaan, jotta muutos tulee voimaan. @@ -2122,7 +2122,7 @@ Hallitse Matrix-tiliisi linkitettyjä sähköpostiosoitteita ja puhelinnumeroita Aseta tilille uusi salasana… Katselet ilmoitusta! Napsauta minua! - Napsauta ilmoitusta. Jos ilmoitusta ei näy, tarkista järjestelmäasetukset. + Napsauta ilmoitusta. Jos ilmoitusta ei näy, tarkasta järjestelmäasetukset. Ilmoitusnäyttö Vianmääritys Ilmoitustapa diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 315e3fe978..31eb24371c 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -100,7 +100,7 @@ %1$s a supprimé %2$s comme adresse pour ce salon. - %1$s a supprimé %3$s comme adresses pour ce salon. + %1$s a supprimé %2$s comme adresses pour ce salon. %1$s a ajouté %2$s et supprimé %3$s comme adresses pour ce salon. %1$s a défini %2$s comme adresse principale pour ce salon. @@ -811,7 +811,7 @@ Prendre une photo Prendre une vidéo Statistiques d’utilisation - Utiliser la caméra native + Utiliser la caméra de l’appareil Rapport d’anomalie Attention ! @@ -1481,7 +1481,7 @@ Envoyer un nouveau message privé Voir le répertoire des salons Nom ou identifiant (#exemple:matrix.org) - Activer le balayage pour répondre dans les l’historique + Activer le balayage pour répondre dans l’historique Lien copié dans le presse-papiers Gestionnaire d’intégrations Aucun gestionnaire d’intégrations n’est configuré. @@ -2182,7 +2182,7 @@ Retour à l’appel Annuler l’invitation Ignorer l’utilisateur - Ignorer cet utilisateur aura pour effet de supprimer ses messages des espaces que vous partagez. + Ignorer cet utilisateur aura pour effet de supprimer ses messages des salons que vous partagez. \n \nVous pouvez annuler cette action à tout moment dans les paramètres généraux. Annuler l’invitation @@ -2372,10 +2372,10 @@ Aucune adresse e-mail n’a été ajoutée à votre compte Adresses e-mail Aucun numéro de téléphone n’a été ajouté à votre compte - La recherche dans les espaces chiffrés n\'est pas encore prise en charge. + La recherche dans les salons chiffrés n\'est pas encore prise en charge. Filtrer les utilisateurs exclus Ne plus ignorer cet utilisateur aura pour effet de ré-afficher ses messages. - expulser un utilisateur le supprimera de cet espace. + expulser un utilisateur le supprimera de ce salon. \n \nPour l’empêcher de revenir, vous devez plutôt le bannir. Motif d’expulsion @@ -2526,8 +2526,8 @@ Rejoindre Consulter d’abord - 1 appel en cours (%1$d) ⋅ 1 appel en attente - 1 appel en cours (%1$d) ⋅ %2$d appels en attente + 1 appel en cours (%1$s) ⋅ 1 appel en attente + 1 appel en cours (%1$s) ⋅ %2$d appels en attente Appel en attente @@ -2643,16 +2643,16 @@ Vous avez modifié les adresses alternatives de ce salon. %1$s a modifié les adresses alternatives de ce salon. - Vous avez supprimé l’adresse alternative %2$s de ce salon. - Vous avez supprimé les adresses alternatives %2$s de ce salon. + Vous avez supprimé l’adresse alternative %1$s de ce salon. + Vous avez supprimé les adresses alternatives %1$s de ce salon. %1$s a supprimé l’adresse alternative %2$s de ce salon. %1$s a supprimé les adresses alternatives %2$s de ce salon. - Vous avez ajouté %2$s comme adresse alternative pour ce salon. - Vous avez ajouté %2$s comme adresses alternatives pour ce salon. + Vous avez ajouté %1$s comme adresse alternative pour ce salon. + Vous avez ajouté %1$s comme adresses alternatives pour ce salon. %1$s a ajouté %2$s comme adresse alternative pour ce salon. @@ -2668,4 +2668,15 @@ %1$s a mis fin à la téléconférence Vous avez démarré la téléconférence Téléconférence démarrée par %1$s + Êtes-vous sûr de vouloir supprimer tous les messages non envoyés dans ce salon \? + Supprimer les messages non envoyés + Messages non envoyés + Voulez-vous annuler l’envoi du message \? + Supprimer tous les messages en échec + Échec + Envoyé + Envoi + Afficher tous les salons dans le répertoire, y compris ceux au contenu choquant. + Afficher les salons au contenu choquant + Répertoire des salons \ No newline at end of file diff --git a/vector/src/main/res/values-ga/strings.xml b/vector/src/main/res/values-ga/strings.xml new file mode 100644 index 0000000000..ee84da7b92 --- /dev/null +++ b/vector/src/main/res/values-ga/strings.xml @@ -0,0 +1,165 @@ + + + CUAIRTEOIRÍ + Socruithe + Comhaid + Daoine + Ceadanna + Tabhair neamhaird ar + Logáil amach + Cuir muinín i + Cuardaigh + "%1$s, " + Cúis + Taispeáin na teachtaireachtaí an úsáideora seo + Tabhair neamhaird ar teachtaireachtaí an úsáideora seo + Bain ceadanna + Luaigh + Caith amach + Bain an coisc + Coisc + Tabhair cuireadh + SEISIÚIN + GLAOIGH AR + Díomhaoin + As líne + Ar Líne + Cruthaigh + + %dl + %dl + %dl + %dl + %dl + + + %du + + %du + %du + %du + + + %dn + + + %dn + %dn + + + + + + %ds + + + Ag sioncronú… + Diúltaigh + Réamhamharc + Téigh isteach + Bain + Lean ar aghaidh + NÍL + + Sábháilte + Eolas + Fan + Tosaigh arís + ag Glaoch ar… + Glaoigh ar + Glaonna + Inniu + Inné + Beag + Meánach + Mór + Bunchóip + Pasfhocal + Léim + Cuir isteach + Ar Ais + Tosaigh + Gléas cinn + Callaire + Guthán + Cuardaigh + Ainm úsáideora + Léite + Pobail + Tabhair cuireadh + Seomraí + Comhráite + Cuirí + Pobail + Seomraí + Daoine + Ceanáin + Fógraí + Tús + Rath + Earráid + Rabhadh + Deimhniú + Fill + Cuir as feidhm + Neamhfoilsigh + Athraigh + Cuir + Cóipeáil + Dún + Oscail + Stairiúil + Gníomhartha + Fág + Diúltaigh + Glac + Diúltaigh + Athbhreithnigh + Déan neamhaird de + Tobscoir + Críochnaithe + Léim + Glac + As líne + Tabhair cuireadh + + Fís + Guth + Athshocraigh + Cuir uait + Cuir ar sos + Cuir ar siúl + Dícheangail + Cúlghair + Níl aon cheann + Athainmnigh + Bain amach + Nasc buan + Seol ar aghaidh + Níos deireanaí + Glan + Labhair + Roinn le + Íoslódáil + Luaigh + Bain + Athsheol + Seol + Fan + Fág + Sábháil + Cealaigh + Ceart go leor + Ag lódáil… + Stairiúil + Socruithe + Seomra + Teachtaireachtaí + Ag sioncronú… + Saincheaptha + Réamhshocrú + Modhnóir + Riarthóir + aon duine. + %1$s: %2$s + \ No newline at end of file diff --git a/vector/src/main/res/values-ga/strings_no_weblate.xml b/vector/src/main/res/values-ga/strings_no_weblate.xml new file mode 100644 index 0000000000..ec03f726fd --- /dev/null +++ b/vector/src/main/res/values-ga/strings_no_weblate.xml @@ -0,0 +1,8 @@ + + + + ga + IE + Latn + + \ No newline at end of file diff --git a/vector/src/main/res/values-gl/strings.xml b/vector/src/main/res/values-gl/strings.xml index bbfe838ca7..61beb07197 100644 --- a/vector/src/main/res/values-gl/strings.xml +++ b/vector/src/main/res/values-gl/strings.xml @@ -830,4 +830,28 @@ Copia de apoio da chave Iniciando o servizo Por defecto no sistema + Debido á falta de permisos, esta acción non é posible. + Iniciar Chat + Restablecer + Desbotar + Deter + Reproducir + Desconectar + Revogar + Nada + Permanecer + Vas perder o acceso ás túas mensaxes cifradas a non ser que fagas unha copia de apoio das chaves antes de desconectar. + Copiar + Tes a certeza\? + Usa a Copia de apoio das Chaves + Copiando as chaves… + Non quero as miñas mensaxes cifradas + A Copia Segura das Chaves está activa para tódalas túas sesións para evitar perder o acceso ás mensaxes cifradas. + Estase realizando a copia de apoio. Se desconectas agora perderás o acceso ás mensaxes cifradas. + Vas perdelas mensaxes cifradas se desconectas agora + Non rematou a copia das chaves, agarda… + Sincr. inicial: +\nDescargando datos… + Sincr. inicial: +\nAgardando resposta do servidor… \ No newline at end of file diff --git a/vector/src/main/res/values-in/strings.xml b/vector/src/main/res/values-in/strings.xml index af8863355c..3afab6fa23 100644 --- a/vector/src/main/res/values-in/strings.xml +++ b/vector/src/main/res/values-in/strings.xml @@ -1,11 +1,9 @@ - + Undang dari %s Undangan Ruang %1$s dan %2$s - Ruang kosong - %1$s dan %2$d yang lain @@ -30,37 +28,30 @@ Panggilan Video Balasan Cepat Peringatan - Konfirmasi Buka Tutup Nonaktifkan - Favorit Cari ruang Cari favorit Cari orang Cari ruang - Direktori Pengguna Tidak ada hasil - Ruang umum belum tersedia Direktori ruang Kirim log Deskripsikan kendala Anda di sini Laporan bug telah berhasil dikirimkan Baca - Daftar Masuk Copot akun URL Server Mula Cari - Mulai Obrolan Baru Ambil foto atau video - Kirim Password Password Baru @@ -85,29 +76,24 @@ Tidak dapat memulai panggilan Keluar Offline - Pencarian global Tandai semua sudah dibaca Orang Ruang - Undangan Percakapan Buku alamat lokal Prioritas rendah - Hanya kontak Matrix Ruang Laporan bug "Aplikasi gagal saat terakhir digunakan. Apakah Anda ingin membuka halaman laporan kegagalan?" - Gabung di Ruang URL Server Identity Mulai Panggilan Suara Masuk Buat Akun Mulai Panggilan Video - Kirim file Password terlalu pendek (min 6) Alamat email ini sudah terdefinisi. @@ -124,35 +110,29 @@ Tidak bisa masuk Tidak bisa registrasi Masukkan URL yang benar - Nama pengguna sudah terpakai Asli Besar Sedang Kecil - Kemarin Batalkan unduhan? Batalkan unggahan? Hari ini - Nama ruang Panggilan terhubung Menyambungkan panggilan… Panggilan diakhiri - Informasi Tersimpan Simpan di Downloads? YA TIDAK Lanjut - Hapus Gabung Pratinjau Tolak - Nanti Kirim Saja ${app_name} belum diijinkan untuk mengakses kontak lokal @@ -169,13 +149,12 @@ Pilih direktori ruang Terdapat perangkat tidak diketahui di ruang Saya verifikasi bahwa kuncinya sesuai - perangkat tidak diketahui Terverifikasi Jejak Percakapan - Hapus - Panggilan massal sedang berlangsung.\nBergabunglah lewat %1$s atau %2$s. + Panggilan massal sedang berlangsung. +\nBergabung sebagai %1$s atau %2$s suara video Beberapa fitur tidak dapat digunakan karena aplikasi belum mendapat ijin… @@ -189,14 +168,12 @@ %d pengguna - Kirim tampilan layar Mohon uraikan bug tersebut. Apa yang Anda lakukan? Apa yang Anda harapkan terjadi? Apa yang sebenarnya terjadi? Log dari klien akan dikirim bersama laporan gangguan ini untuk mendalami kendala yang Anda temukan. Laporan gangguan ini, termasuk log dan rekalayar, tidak akan dilihat oleh khalayak umum. Jika Anda hanya ingin mengirimkan tulisan di atas, silahkan hapus centang: Sepertinya Anda mengguncang telepon akibat frustrasi. Apakah Anda ingin membuka halaman laporan bug? Pengiriman laporan bug gagal (%s) Kemajuan (%s%%) - Kirim ke Nama Pengguna Nama pengguna dan/atau kata sandi salah @@ -207,18 +184,17 @@ Alamat email atau nomor telpon belum dimasukkan Gunakan server lain (lanjutan) Silahkan periksa email Anda untuk melanjutkan pendaftaran - Pendaftaran dengan email sekaligus nomor telpon belum didukung sampai API dihadirkan. Hanya nomor telpon yang akan digunakan. - -Anda dapat menambah email di profile Anda dalam pengaturan nantinya. + Pendaftaran dengan menggunakan email dan nomor telepon tidak didukung sampai API dihadirkan. Hanya nomor telepon anda yang akan digunakan. +\n +\nAnda dapat menambahkan email di profil anda melalui menu pengaturan. Nama pengguna yang terpakai Untuk menyetel ulang kata sandi Anda, silahkan masukkan alamat email yang tertaut ke akun Anda: Anda perlu memasukkan alamat email yang tertaut pada akun. - "Selembar email telah dikirim ke %s. Setelah Anda mengikuti tautan yang termuat di dalamnya, klik yang di bawah." + Surel telah dikirim ke alamat %s. Setelah Anda mengikuti tautan yang termuat di dalamnya, klik di bawah. Verifikasi alamat email gagal: pastikan tautan yang termuat di email telah diklik Kata sandi Anda telah disetel ulang. - -Anda telah dikeluarkan dari semua perangkat dan tidak lagi menerima pemberitahuan dorongan. Untuk menerima kembali pemberitahuan, masuk kembali dengan tiap perangkat. - +\n +\nAnda telah dikeluarkan dari seluruh sesi dan tidak lagi menerima push notification. Untuk kembali menerima pemberitahuan, masuklah kembali dengan tiap perangkat. Tidak dapat masuk: Gangguan jaringan Tidak dapat mendaftar: Gangguan jaringan Tidak dapat mendaftar : gagal memastikan kepemilikan alamat email @@ -228,58 +204,47 @@ Anda telah dikeluarkan dari semua perangkat dan tidak lagi menerima pemberitahua Tidak berisi JSON yang sah Pengajuan yang dikirimkan terlalu banyak Tautan email masih belum diklik - Baca Daftar Penerimaan - - "Kirim sebagai " + Kirim sebagai %d d %1$dm %2$dd - Topik ruang - Memanggil… Panggilan Masuk Panggilan Video Masuk Panggilan Suara Masuk Panggilan Sedang Berlangsung… - Hubungan Media Gagal Server Identitas: Tidak dapat memulai kamera panggilan terjawab di tempat lain Ambil gambar atau video Tidak bisa merekam video - - ${app_name} membutuhkan permisi atas akses galeri foto dan video Anda untuk mengirim dan menyimpan lampiran. - -Harap berikan akses pada halaman berikut agar berkas dapat dikirim dari ponsel Anda. + ${app_name} membutuhkan izin untuk mengakses galeri foto dan video Anda untuk mengirim dan menyimpan lampiran. +\n +\nHarap berikan akses pada halaman berikut ini agar berkas dapat dikirim dari ponsel Anda. ${app_name} membutuhkan izin Anda untuk mengakses kamera untuk mengambil gambar dan melakukan panggilan video. - - -Harap berikan akses pada halaman berikut agar dapat melakukan panggilan. + " +\n +\nHarap berikan akses pada halaman berikut ini agar dapat melakukan panggilan." ${app_name} membutuhkan permisi atas akses mikrofon Anda untuk melakukan panggilan audio. - - -Harap berikan akses pada halaman berikut agar dapat melakukan panggilan. - ${app_name} membutuhkan permisi atas akses kamera dan mikrofon Anda untuk melakukan panggilan video. - -Harap berikan akses pada halaman selanjutnya untuk melakukan panggilan. + " +\n +\nHarap berikan akses pada halaman berikut ini agar dapat melakukan panggilan." + ${app_name} membutuhkan izin untuk mengakses kamera dan mikrofon Anda untuk melakukan panggilan video. +\n +\nHarap berikan akses pada halaman berikut ini untuk melakukan panggilan. Tema Terang Tema Kelam Tema Gelap - - Sedang Sinkronisasi + Menyinkronkan… Pemberitahuan Berisik Pemberitahuan Tenteram - Laporan Gangguan Detail Komunitas Kirimkan Sticker - Lisensi Pihak Ketiga - Memuat… - Unduh Bicaralah Bersihkan @@ -287,69 +252,50 @@ Harap berikan akses pada halaman selanjutnya untuk melakukan panggilan. Keluar Tindakan Komunitas - Mencari komunitas - Peringatan Sistem - Undang Komunitas Tidak ada grup - Mohon deskripsikan dengan bahasa Inggris apabila memungkinkan. Guncang perangkat untuk laporan gangguan - Kirim Pesan Suara - Apa benar Anda ingin memulai percakapan baru dengan %s? Apa benar Anda ingin memulai panggilan suara? Apa benar Anda ingin memulai panggilan video? - Kirim Sticker Ambil foto Ambil video - Saat ini Anda belum memiliki pak stiker. \n \nMau tambah sekarang\? - lanjutkan dengan… Maaf, tidak ada aplikasi eksternal yang mendukung apa yang ingin dilakukan. - Meminta ulang kunci enkripsi dari perangkat Anda yang lain. - Permintaan kunci terkirim. - Permintaan terkirim Jalankan ${app_name} di perangkat yang dapat mendekripsi pesan tersebut agar kunci dapat dikirim ke perangkat ini. - Daftar Grup - %d perubahan keanggotaan - Panggilan ${app_name} memerlukan permisi untuk mengakses daftar kontak agar dapat mencari pengguna Matrix lain berdasarkan email dan nomor telepon. Ijinkan akses lewat halaman selanjutnya untuk menemukan pengguna ${app_name} yang terdapat di daftar kontak Anda. - ${app_name} memerlukan permisi akses daftar kontak Anda untuk menemukan pengguna Matrix lain berdasarkan email dan nomor telepon mereka. - -Bolehkah ${app_name} mengakses daftar kontak Anda? - - "Maaf. Tidak dapat dilakukan karena belum menerima permisi" - + ${app_name} memerlukan izin untuk mengakses daftar kontak Anda untuk menemukan pengguna Matrix lain berdasarkan email dan nomor telepon mereka. +\n +\nApakah anda bersedia bila ${app_name} mengakses daftar kontak Anda\? + Mohon Maaf. Aksi ini tidak dapat dilakukan karena belum menerima izin terkait Daftar Anggota Buka kop Menyinkronkan… Arahkan ke pesan pertama yang belum terbaca. - Anda telah diundang untuk bergabung ke ruang ini oleh %s Undangan ini dikirim oleh %s, yang tidak terhubung dengan akun ini. -Anda mungkin ingin masuk dengan akun lain, atau tambahkan email ini ke akun Anda. +\nAnda mungkin ingin masuk dengan akun lain, atau tambahkan email ini ke akun Anda. Anda sedang berupaya untuk mengakses %s. Maukah Anda bergabung untuk berpartisipasi dalam diskusi ini? Ini adalah pratinjau untuk ruang ini. Interaksi dengan ruang belum dapat dilakukan. - Percakapan Baru Tambah anggota @@ -359,7 +305,6 @@ Anda mungkin ingin masuk dengan akun lain, atau tambahkan email ini ke akun Anda %d anggota 1 anggota - %dd @@ -372,23 +317,19 @@ Anda mungkin ingin masuk dengan akun lain, atau tambahkan email ini ke akun Anda $dh - Tinggalkan ruang Apa benar Anda ingin meninggalkan ruang ini? Apa benar Anda ingin mengeluarkan %s dari percakapan ini? Buat - Online Offline Berdiam Diri %1$s sekarang %1$s %2$s yang lalu - PERALATAN ADMIN PANGGIL PERCAKAPAN LANGSUNG PERANGKAT - Undang Tinggalkan ruang ini Keluarkan dari ruang ini @@ -402,15 +343,12 @@ Anda mungkin ingin masuk dengan akun lain, atau tambahkan email ini ke akun Anda ID Pengguna, Nama atau email Sebut Tunjukkan Daftar Perangkat - Anda tidak akan dapat membalik perubahan ini karena Anda mengangkat pengguna ini agar memiliki kuasa yang setara dengan Anda. -Yakin? - - Apa benar Anda ingin melarang pengguna ini dari percakapan ini? - + Anda tidak akan dapat mengembalikan perubahan ini setelah Anda mengangkat pengguna ini agar memiliki kuasa yang setara dengan Anda. +\nApakah anda yakin untuk melanjutkan\? + Melakukan banning pengguna akan mengeluarkannya dari ruangan ini dan mencegahnya untuk kembali masuk. Apa benar Anda ingin mengundang %s ke percakapan ini? %1$s dan %2$s %1$s %2$s - Gagal terjawab oleh pihak lain. ruang "%1$s, " @@ -418,11 +356,9 @@ Yakin? KONTAK LOKAL (%d) DIREKTORI PENGGUNA (%s) Pengguna Matrix saja - Undang pengguna dengan ID Masukkan satu atau lebih alamat email atau ID Matrix Email atau ID Matrix - Cari %s sedang mengetik… %1$s & %2$s sedang mengetik… @@ -443,7 +379,6 @@ Yakin? %d pesan baru - Percaya Tidak percaya Keluar @@ -455,7 +390,6 @@ Yakin? Sertifikat ini tidak lagi sesuai dengan yang dipercayai oleh perangkat Anda sebelumnya. Ini SANGAT JANGGAL. Kami rekomendasikan Anda untuk TIDAK MENERIMA sertifikat baru ini. Terdapat perubahan sertifikat yang tidak lagi dipercayai perangkat. Server mungkin telah memperbaharui sertifikatnya. Hubungi administrator server untuk pencocokan sidik jari. Hanya terima sertifikat ini apabila administrator server telah menerbitkan sidik jari yang cocok dengan yang tertera di atas. - Detail Ruang Orang Berkas @@ -466,14 +400,12 @@ Yakin? ID tidak sesuai. Seharusnya alamat email atau ID Matrix semisal \'@localport:domain\' DIUNDANG BERGABUNG - Alasan laporan konten ini - Apa benar Anda ingin menyembunyikan semua pesan dari pengguna ini? - -Perhatikan bahwa tindakan ini akan memulai ulang aplikasi dan mungkin cukup memakan waktu. + Apa Anda ingin menyembunyikan seluruh pesan dari pengguna ini\? +\n +\nHarap diperhatikan bahwa tindakan ini akan me-restart aplikasi dan mungkin akan memakan waktu beberapa saat. Batalkan Unggahan Batalkan Unduhan - Cari Saring anggota ruang Tiada hasil @@ -481,7 +413,6 @@ Perhatikan bahwa tindakan ini akan memulai ulang aplikasi dan mungkin cukup mema PESAN ORANG BERKAS - GABUNG DIREKTORI FAVORIT @@ -493,7 +424,6 @@ Perhatikan bahwa tindakan ini akan memulai ulang aplikasi dan mungkin cukup mema Gabung ke ruang Gabung ke ruang Ketik id atau alias ruang - Jelajahi direktori %d ruang @@ -502,7 +432,6 @@ Perhatikan bahwa tindakan ini akan memulai ulang aplikasi dan mungkin cukup mema %1$s ruang ditemukan untuk %2$s Mencari direktori… - Semua pesan (berisik) Semua pesan Hanya sebutan @@ -513,7 +442,6 @@ Perhatikan bahwa tindakan ini akan memulai ulang aplikasi dan mungkin cukup mema Tinggalkan Percakapan Lupakan Tambahkan Shortcut pada Homescreen - Pesan Pengaturan Versi @@ -521,7 +449,6 @@ Perhatikan bahwa tindakan ini akan memulai ulang aplikasi dan mungkin cukup mema Pemberitahuan pihak ketiga Hak Cipta Kebijakan Pribadi - Gambar Profil Nama Layar Email @@ -530,7 +457,6 @@ Perhatikan bahwa tindakan ini akan memulai ulang aplikasi dan mungkin cukup mema Tambahkan nomor telepon Tampilkan info aplikasi dalam pengaturan sistem. Info aplikasi - Kerahasiaan pemberitahuan Normal Kerahasiaan diperlemah @@ -540,12 +466,10 @@ Perhatikan bahwa tindakan ini akan memulai ulang aplikasi dan mungkin cukup mema • Isi pesan pemberitahuan tersimpan langsung dengan aman di homeserver Matrix • Pemberitahuan memuat meta data dan data pesan • Pemberitahuan tidak akan menunjukkan isi pesan - Suara pemberitahuan Perbolehkan pemberitahuan untuk akun ini Perbolehkan pemberitahuan untuk perangkat ini Nyalakan layar selama 3 detik - Pesan yang berisikan nama layarku Pesan berisikan nama layarku Pesan percakapan empat mata @@ -553,13 +477,11 @@ Perhatikan bahwa tindakan ini akan memulai ulang aplikasi dan mungkin cukup mema Kapan saya diundang ke suatu ruang Undangan panggilan Pesan yang dikirim bot - Mulai sedari boot Sinkronisasi di balik layar Perbolehkan sinkronisasi di balik layar Batas waktu permohonan sinkronisasi Masa tunda sebelum permohonan berikutnya - Versi versi olm Syarat & ketentuan @@ -567,9 +489,7 @@ Perhatikan bahwa tindakan ini akan memulai ulang aplikasi dan mungkin cukup mema %d ruang %1$s dalam %2$s - Cari sejarah - Anda butuh permisi untuk mengurus widget di ruang ini Pembuatan widget gagal Buat panggilan konferensi dengan jitsi @@ -577,7 +497,6 @@ Perhatikan bahwa tindakan ini akan memulai ulang aplikasi dan mungkin cukup mema %d widget aktif - Tidak dapat membuat widget. Gagal mengirim permohonan. Tingkat energi harus bilangan positif. @@ -592,16 +511,13 @@ Perhatikan bahwa tindakan ini akan memulai ulang aplikasi dan mungkin cukup mema Gunakan kamera bawaan Gunakan tombol enter keyboard untuk mengirim pesan Kirim pesan suara - Anda menambahkan perangkat baru \'%s\', yang sedang meminta kunci enkripsi. Perangkat Anda yang belum terverifikasi \'%s\' sedang meminta kunci enkripsi. Mulai verifikasi Bagikan tanpa verifikasi Abaikan verifikasi - Peringatan! Panggilan konferensi masih sedang pengembangan dan mungkin belum dapat diandalkan. - Kesalahan perintah Perintah tak dikenal: %s Tunjukkan tindakan @@ -616,116 +532,90 @@ Perhatikan bahwa tindakan ini akan memulai ulang aplikasi dan mungkin cukup mema Ubah nama panggilan layar Anda Mati/Nyalakan markdown Untuk memperbaiki kepengurusan Apps Matrix - Mati Berisik - Pesan terenkripsi - Buat Buat Komunitas Nama komunitas Contoh Id Komunitas contoh - Pangkal Orang Ruang Tidak ada pengguna - Ruang Telah bergabung Telah Diundang Saring anggota grup Saring ruang grup - %d anggota - %d ruang Admin komunitas belum menyediakan deskripsi panjang untuk komunitas ini. - Anda telah dikeluarkan dari %1$s oleh %2$s Anda telah dilarang dari %1$s oleh %2$s Alasan: %1$s Gabung lagi Lupakan ruang - Untuk terus menggunakan homeserver %1$s Anda harus membaca dan menyetujui syarat dan ketentuan. Avatar - Baca sekarang - Deaktivasi Akun - Ini akan mengakibatkan akun Anda tidak dapat digunakan secara permanen. Anda tidak akan dapat masuk dan orang lain tidak dapat mendaftar ulang dengan ID pengguna yang sama. Ini akan mengakibatkan akun Anda keluar dari semua ruang di mana Anda berpartisipasi dan menghapus semua detail akun dari identity server Anda. Tindakan ini tidak dapat dibalikkan. - -Mendeaktivasi akun Anda tidak semerta membuat kami melupakan pesan-pesan yang Anda kirim. Jika Anda ingin kami melupakan pesan-pesan Anda, mohon centang kotak berikut. - -Pembacaan pesan di Matrix serupa dengan email. Dengan kami melupakan pesan-pesan Anda berarti pesan-pesan yang Anda kirim tidak akan dibagikan kepada pengguna baru atau yang belum terdaftar, tapi pengguna yang terdaftar dan telah dapat mengakses pesan-pesan tersebut masih bisa membaca rangkap yang mereka simpan. + Ini akan mengakibatkan akun Anda tidak dapat digunakan secara permanen. Anda tidak akan dapat masuk dan orang lain tidak dapat mendaftar ulang dengan ID pengguna yang sama. Ini akan mengakibatkan akun Anda keluar dari semua ruang tempat Anda berpartisipasi serta menghapus semua detail akun dari identity server Anda. Tindakan ini tidak dapat diubah kembali. +\n +\nMenonaktifkan akun Anda tidak serta-merta membuat kami melupakan pesan-pesan yang Anda kirim. Jika Anda ingin kami melupakan pesan-pesan Anda, mohon centang kotak berikut. +\n +\nKeterbacaan pesan di Matrix serupa dengan email. Dengan kami melupakan pesan-pesan Anda, berarti pesan-pesan yang Anda kirim tidak akan dibagikan kepada pengguna baru ataupun yang belum terdaftar. Tetapi pengguna yang terdaftar dan telah dapat mengakses pesan-pesan tersebut masih bisa membaca rangkap yang mereka simpan. Mohon lupakan semua pesan yang telah kukirim ketika akunku dideaktivasi (Peringatan: ini akan mengakibatkan pengguna mendatang membaca percakapan yang tidak lengkap) Untuk melanjutkan, masukkan kata sandi Anda: Deaktivasi Akun - Mohon masukkan kata sandi Anda. Ruang ini telah berubah dan tidak lagi aktif Percakapan berlanjut di sini Ruang ini adalah kelanjutan percakapan lain Klik di sini untuk melihat pesan lama - Melampaui Batasan Sumber Daya Kontak Administrator - kontak administrator layanan Anda - Homeserver ini telah melampaui salah satu batas sumber dayanya sehingga beberapa pengguna tidak dapat masuk. Homeserver ini telah melampaui salah satu batasan sumber dayanya. - Homeserver ini telah mencapai batas Pengguna Aktif Bulanan sehingga beberapa pengguna tidak dapat masuk. Homeserver ini telah mencapai batas Pengguna Aktif Bulanan. - Mohon %s untuk meningkatkan batasan ini. Mohon %s untuk terus menggunakan layanan ini. - Impor kunci ruang terenkripsi Impor kunci ruang Impor kunci dari berkas lokal Impor Hanya enkripsi ke perangkat terverifikasi Jangan kirim pesan terenkripsi ke perangkat yang tidak terverifikasi dari perangkat ini. - TIDAK terverifikasi Ter-blacklist - tidak ada - Verifikasi Batalkan verifikasi Blacklist Batalkan blacklist - Verifikasi perangkat Untuk memastikan perangkat dapat dipercaya, mohon kontak pengguna dengan medium lain (misalnya tatap muka atau panggilan telepon) dan tanya apakah kunci yang mereka lihat di Pengaturan Pengguna untuk perangkat ini cocok dengan kunci berikut: Apabila cocok, tekan tombol verifikasi berikut. Apabila tidak, seseorang sedang menyadap perangkat ini dan mungkin perlu diblokir. Di masa mendatang proses verifikasi ini akan dimutakhirkan. - - Ruang ini terisi oleh perangkat tak dikenal yang belum diverifikasi. -Ini berarti tidak ada jaminan pengguna perangkat tersebut sesuai dengan klaim mereka. -Kami sarankan Anda untuk memverifikasi untuk setiap perangkat terlebih dahulu sebelum melanjutkan, tapi Anda boleh mengirim ulang pesan tanpa verifikasi jika Anda mau. - -Perangkat tak dikenal: - + Ruang ini terdapat sesi yang yang belum diverifikasi. +\nIni artinya, tidak ada jaminan pengguna sesi tersebut sesuai dengan klaim mereka. +\nKami sarankan Anda untuk memverifikasi untuk setiap sesi terlebih dahulu sebelum melanjutkan, namun Anda juga boleh mengirim ulang pesan tanpa verifikasi bila anda memilih demikian. +\n +\nSesi yang tak dikenal: Server mungkin belum siap atau kelebihan beban Ketik homeserver yang ingin Anda lihat daftar ruang publiknya Semua ruang dalam server %s Semua ruang bawaan %s - Ketik di sini… - %d pesan pemberitahuan yang belum terbaca @@ -734,7 +624,6 @@ Perangkat tak dikenal: Prioritas rendah Tidak Ada - Akses dan visibilitas Daftarkan ruang ini di direktori ruang Pemberitahuan @@ -742,19 +631,15 @@ Perangkat tak dikenal: Singkapan Sejarah Ruang Siapa yang bisa membaca sejarah? Siapa yang bisa mengakses ruang ini? - Siapapun Hanya anggota (dimulai sejak opsi ini dipilih) Hanya anggota (dimulai sejak mereka diundang) Hanya anggota (dimulai sejak mereka bergabung) - Ruang harus memiliki alamat agar dapat ditautkan. Hanya orang yang telah diundang Siapapun yang tahu tautan ruang, selain tamu Siapapun yang tahu tautan ruang, termasuk tamu - Pengguna yang dilarang - Lanjutan ID internal ruang ini Alamat @@ -765,38 +650,28 @@ Perangkat tak dikenal: Anda perlu keluar dulu untuk mengaktifkan enkripsi. Enkripsi ke perangkat terverifikasi saja Jangan mengirim pesan terenkripsi ke perangkat yang belum diverifikasi di ruang ini dengan perangkat ini. - Ruang ini tidak punya alamat lokal Alamat baru (misalnya #foo:matrix.org) - Ruang ini tidak menunjukkan flair untuk komunitas manapun ID komunitas baru (misalnya +foo:matrix.org) ID komunitas tidak valid \'%s\' bukan ID komunitas yang valid - - Format alias tidak valid \'%s\' bukanlah format alias yang valid Anda tidak akan mendapat alamat utama untuk ruang ini. Peringatan alamat utama - Tentukan sebagai Alamat Utama Jangan tentukan sebagai Alamat Utama Salin ID Ruang Salin Alamat Ruang - Enkripsi diaktifkan untuk ruang ini. Enkripsi dinonaktifkan untuk ruang ini. Aktifkan enkripsi -(peringatan: tidak lagi bisa dinonaktifkan!) - +\n(peringatan: tidak dapat dinonaktifkan kembali!) Direktori Tema - %s sedang mencoba memuat titik tertentu di rentang waktu ruang ini tapi belum dapat menemukannya. - Informasi enkripsi ujung-ke-ujung - Informasi peristiwa Id pengguna Kunci identitas Curve25519 @@ -804,7 +679,6 @@ Perangkat tak dikenal: Algoritma ID Sesi Kesalahan dekripsi - Informasi perangkat pengirim Nama perangkat Nama @@ -812,17 +686,15 @@ Perangkat tak dikenal: Kunci perangkat Verifikasi Sidik jari Ed25519 - Ekspor kunci ruang terenkripsi Ekspor ruang kunci Ekspor kunci ke berkas lokal Ekspor Masukkan kata sandi Tegaskan kata sandi - Kunci ruang terenkripsi telah disimpan di \'%s\'. - -Peringatan: berkas ini mungkin ikut terhapus jika aplikasi dihapus. - + Kunci E2E ruang tersebut telah disimpan di \'%s\'. +\n +\nPeringatan: berkas ini mungkin ikut terhapus bila aplikasi ini dihapus. Mendengarkan peristiwa Pemberitahuan pihak ketiga Hak Cipta @@ -830,7 +702,6 @@ Peringatan: berkas ini mungkin ikut terhapus jika aplikasi dihapus. Bersihkan cache Bersihkan cache media Pertahankan media - Pengaturan pengguna Pemberitahuan Pengguna yang diabaikan @@ -850,23 +721,18 @@ Peringatan: berkas ini mungkin ikut terhapus jika aplikasi dihapus. Tampilkan waktu kirim dalam format 12 jam Bergetar ketika menyebut seorang pengguna Pratinjau media sebelum dikirim - Deaktivasi akun Deaktivasi akunku - Kerahasiaan Notifikasi ${app_name} dapat beroperasi di balik layar untuk mengurus pemberitahuan Anda dengan aman dan rahasia. Ini dapat mempengaruhi masa tahan baterai. Kabulkan permisi Pilih opsi lain - Analitik Kirim data analitik ${app_name} mengumpulkan data analitik anonim dalam upaya kami meningkatkan aplikasi. Mohon aktifkan analitik untuk membantu kami meningkatkan ${app_name}. Ya, saya ingin membantu! - Mode hemat data - Rincian perangkat ID Nama @@ -874,42 +740,34 @@ Peringatan: berkas ini mungkin ikut terhapus jika aplikasi dihapus. Terakhir terlihat %1$s @ %2$s Operasi ini membutuhkan otentikasi tambahan. -Untuk melanjutkan, masukkan kata sandi Anda. +\nUntuk melanjutkan operasi ini, mohon masukkan kata sandi anda. Otentikasi Kata Sandi: Serahkan - Masuk sebagai Home Server Server Identitas - Antarmuka pengguna Bahasa Pilih bahasa - Verifikasi Tertunda Mohon cek email Anda dan klik tautan yang termuat di sana. Setelah itu, klik lanjutkan. Tidak dapat memverifikasi alamat email. Mohon periksa email Anda dan klik tautan yang termuat di sana. Setelah itu, klik lanjutkan. Alamat email ini telah digunakan. Gagal mengirim email: Alamat email ini tidak dapat ditemukan. Nomor telepon ini telah digunakan. - Ubah kata sandi Sandi lama Sandi baru Ulangi sandi Gagal memperbaharui kata sandi Kata sandi Anda telah diperbaharui - Tunjukkan semua pesan dari %s? - -Tindakan ini akan memulai ulang aplikasi dan mungkin cukup memakan waktu. - + Tunjukkan semua pesan dari %s\? +\n +\nMohon perhatikan bahwa tindakan ini akan me-restart aplikasi dan mungkin akan memakan waktu. Apa benar Anda ingin menyingkirkan sasaran pemberitahuan ini? - Apa benar Anda ingin menyingkirkan %1$s %2$s? - Pilih negara - Negara Mohon pilih negara Nomor telepon @@ -919,44 +777,32 @@ Tindakan ini akan memulai ulang aplikasi dan mungkin cukup memakan waktu.Masukkan kode aktivasi Ada kesalahan ketika memvalidasi nomor telepon Anda Kode - - Flair Anda belum menjadi anggota komunitas manapun saat ini. - 3 hari 1 minggu 1 bulan Selamanya - Foto Ruang Nama Ruang Topik Tanda Ruang Ditandai sebagai: - Favorit Avatar pemberitahu Avatar penerima Demosi pengguna dengan id berikut - Tetap Panggil Terima - Error - Mohon telaah dan terima kebijakan homeserver ini: - Panggilan Gunakan nada dering semula ${app_name} untuk panggilan masuk Nada dering panggilan masuk Pilih nada dering untuk panggilan: - Panggilan Video Sedang Berlangsung… - Keluarkan Alasan - Versi %s Periksa Keadaan Pemberitahuan Hasil diagnosa pemeriksaan keadaan @@ -965,70 +811,58 @@ Tindakan ini akan memulai ulang aplikasi dan mungkin cukup memakan waktu.Diagnosa dasar berlangsung lancar. Apabila Anda masih belum dapat menerima pemberitahuan, mohon kirim laporan bug untuk kami selidiki. Satu atau beberapa ujicoba gagal, coba sugesti yang kami tawarkan. Satu atau beberapa ujicoba gagal, mohon kirim laporan bug untuk kami selidiki. - Pengaturan Sistem. Pemberitahuan diperbolehkan dalam pengaturan sistem. - Pemberitahuan tidak diperbolehkan dalam pengaturan sistem. -Silakan periksa pengaturan sistem. + Notifikasi dinonaktifkan dalam pengaturan sistem. +\nMohon periksa pengaturan sistem anda. Buka Pengaturan - Pengaturan Akun. Pemberitahuan diperbolehkan dalam pengaturan akun Anda. - Pemberitahuan tidak diperbolehkan dalam pengaturan akun Anda. -Mohon periksa pengaturan akun. + Notifikasi dinonaktifkan dalam pengaturan akun anda. +\nMohon periksa pengaturan akun anda. Perbolehkan - Pengaturan Perangkat. Pemberitahuan diperbolehkan untuk perangkat ini. - Pemberitahuan tidak diperbolehkan untuk perangkat ini. -Mohon periksa pengaturan ${app_name}. + Notifikasi tidak diaktifkan pada sesi ini. +\nMohon periksa pengaturan ${app_name}. Perbolehkan - Pemeriksaan Layanan Google Play APK Layanan Google Play ditemukan dan telah diperbaharui. ${app_name} menggunakan Layanan Google Play untuk mendorong pesan tapi tampaknya tidak diatur sebagaimana harusnya. \n%1$s Perbaiki Layanan Google Play - Token Firebase - Sukses mengambil token FCM. -%1$s - Gagal mengambil token FCM. -%1$s - + Sukses mengambil token FCM: +\n%1$s + Gagal mengambil token FCM: +\n%1$s Pendaftaran Token Sukses mendaftarkan token FCM di HomeServer. - Gagal mendaftarkan token FCM di HomeServer. -%1$s - + Gagal mendaftarkan token FCM ke HomeServer: +\n%1$s Layanan Pemberitahuan Layanan Pemberitahuan sedang berjalan. - Layanan Pemberitahuan terhenti. -Coba nyalakan kembali aplikasi. + Layanan notifikasi tidak berjalan. +\nCoba buka ulang aplikasi ini. Mulai Layanan - Nyalakan Layanan Pemberitahuan dengan Otomatis Layanan terhenti dan dinyalakan kembali secara otomatis. Layanan tidak dapat dinyalakan kembali - Mulai ketika menyalakan perangkat Layanan akan dimulai ketika perangkat dinyalakan kembali. Layanan tidak akan mulai ketika perangkat dinyalakan kembali, Anda tidak akan menerima pemberitahuan hingga Anda membuka ${app_name}. Perbolehkan memulai ketika perangkat dinyalakan - Periksa halangan di balik layar - Halangan di balik layar dimatikan untuk ${app_name}. Ujicoba ini harus dijalankan menggunakan jaringan data (bukan WIFI). -%1$s - Halangan di balik layar dinyalakan untuk ${app_name}. -Aktivitas yang dilakukan aplikasi ini akan terhalang ketika beroperasi di balik layar, dan ini dapat mempengaruhi pemberitahuan. -%1$s + Larangan background dinonaktifkan untuk ${app_name}. Percobaan ini sebaiknya dijalankan menggunakan jaringan mobile data (bukan WIFI). +\n%1$s + Larangan background dinonaktifkan untuk ${app_name}. +\nAktivitas yang dilakukan aplikasi ini akan terhalang ketika beroperasi di balik layar, dan ini dapat mempengaruhi pemunculan notifikasi. +\n%1$s Matikan penghalang - Optimisasi Baterai ${app_name} tidak terpengaruh oleh Optimisasi Baterai. Cadangkan Kunci Gunakan Cadangan Kunci - Pencadangan kunci belum selesai, mohon tunggu… Pesan terenkripsi Anda akan hilang apabila Anda mencopot akun sekarang Pencadangan kunci sedang berlangsung. Pesan terenkripsi Anda akan hilang apabila Anda mencopot akun sekarang. @@ -1039,21 +873,17 @@ Aktivitas yang dilakukan aplikasi ini akan terhalang ketika beroperasi di balik Yakin\? Cadangkan Akses ke pesan terenkripsi akan hilang apabila Anda tidak mencadangkan kunci sebelum mencopot akun. - Lewatkan Selesai Hentikan - Anda yakin ingin mencopot akun\? Pengaturan Pemberitahuan Lanjutan Urgensi pemberitahuan lewat kejadian - Pengaturan Sesukanya. Perhatikan bahwa sebagian jenis pesan tersetel diam (mengeluarkan pemberitahuan tanpa suara). Sebagian pemberitahuan dimatikan dalam aturan Anda. Gagal memuat aturan, mohon coba lagi. Periksa Aturan - [%1$s] \nError ini di luar kendali ${app_name} dan menurut Google, error ini muncul ketika terlalu banyak aplikasi terdaftar dengan FCM pada perangkat tersebut. Error ini tidak seharusnya mempengaruhi pengguna biasa. [%1$s] @@ -1061,16 +891,12 @@ Aktivitas yang dilakukan aplikasi ini akan terhalang ketika beroperasi di balik [%1$s] \nError ini di luar kendali ${app_name}. Tidak terdapat akun Google pada perangkat. Mohon buka pengelola akun dan tambahkan akun Google. Tambah Akun - Apabila perangkat tidak sedang diisi atau dipergunakan dengan layar dimatikan, perangkat masuk mode Doze. Ini akan menghalangi aplikasi mengakses jaringan dan menunda tugas, sinkronisasi, dan alarm standar. Abaikan Optimisasi - Kelola Pemberitahuan Berisik Kelola Pemberitahuan Panggilan Kelola Pemberitahuan Diam Pilih warna LED, getaran, suara… - - Pengelolaan Kunci Kriptografi Pratinjau tautan dalam obrolan apabila homeserver mendukung fitur ini. Kirim pemberitahuan mengetik @@ -1082,4 +908,4 @@ Aktivitas yang dilakukan aplikasi ini akan terhalang ketika beroperasi di balik Tunjukkan kejadian bergabung dan meninggalkan Undangan, pengeluaran, dan larangan tidak terpengaruh. Tunjukkan kejadian akun - + \ No newline at end of file diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 39ca954053..a60946c593 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -2668,4 +2668,70 @@ Riprendi Non autorizzato, credenziali di autenticazione valide mancanti Torna + Vuoi veramente eliminare tutti i messaggi non inviati in questa stanza\? + Elimina i messaggi non inviati + Invio dei messaggi fallito + Vuoi annullare l\'invio del messaggio\? + Elimina tutti i messaggi falliti + Fallito + Inviato + Invio in corso + Contenuto dell\'evento + Evento di stato inviato! + Evento inviato! + Evento malformato + Tipo di messaggio mancante + Nessun contenuto + Contenuto dell\'evento + Chiave dello stato + Tipo + Invia evento di stato personalizzato + Modifica contenuto + Eventi di stato + Invia evento di stato + Invia evento personalizzato + Esplora stato stanza + Strumenti Svil + Vedi le conferme di lettura + Non notificare + Notifica senza suono + Notifica con suono + Messaggio non inviato per un errore + Selezionato + Chiudi selettore emoji + Apri selettore emoji + Livello di fiducia di affidabilità + Livello di fiducia di allerta + Livello di fiducia predefinito + Selezionato + Video + Questa stanza ha una bozza non inviata + Alcuni messaggi non sono stati inviati + Elimina avatar + Cambia avatar + Immagine + Importa chiave da file + Apri i widget + Schermata + + %d elemento + %d elementi + + Il limite è sconosciuto. + Il tuo homeserver accetta allegati (file, multimedia, ecc.) di una dimensione fino a %s. + Limite di invio file al server + Versione server + Nome server + Impostazioni stanza + Abbandonare la conferenza attuale e passare all\'altra\? + Versione stanza + Mostra tutte le stanze nell\'elenco, incluse quelle con contenuti espliciti. + Mostra stanze con contenuti espliciti + Elenco delle stanze + Nuovo valore + Cambia + Sinc. iniziale: +\nScaricamento dati… + Sinc. iniziale: +\nIn attesa di risposta dal server… \ No newline at end of file diff --git a/vector/src/main/res/values-kab/strings.xml b/vector/src/main/res/values-kab/strings.xml index 516ccc1caf..53b4ee9214 100644 --- a/vector/src/main/res/values-kab/strings.xml +++ b/vector/src/main/res/values-kab/strings.xml @@ -3,8 +3,8 @@ %1$s: %2$s %1$s t.yuzen tugna. Tuzneḍ tugna. - Tinubga n %s - Tinubga-k•m + Tinnubga n %s + Tinnubga-k•m %1$s yesnulfa-d taxxamt Tesnulfaḍ-d taxxamt-a %1$s inced-d %2$s @@ -2406,4 +2406,74 @@ Sken %d ibenkani swayes i tzemreḍ ad tesneqdeḍ tura Rnu adiwenni usrid amaynut s usulay n Matrix + Sken anasiw n yimujiten + Beddel asentel + Azen ineḍruyen m.room.server_acl + Beddel tisirag + Beddel isem n texxamt + Rmed awgelhen n texxamt + Beddel tansa tagejdant n texxamt + Beddel avaṭar n texxamt + Snifel iwiǧiten + Kkes iznan n wiyaḍ + Gdel iseqdacen + Snifel iɣewwaren + Nced iseqdacen + Azen iznan + Tisirag + Tisirag n texxamt + Taxxamt-a mačči d tazayezt. Ur tzemmreḍ ara ad tedduḍ ɣur-d war tinnubga. + Ṭṭef + Akaram n texxamin + Azal amaynut + Uɣal + Beddel + Am unagraw + Tugiḍ i yinebgawen ad d-asen ɣer da. + %1$s yugi i yinebgawen ad d-asen ɣer da. + Tessirgeḍ inebgawen ad d-asen ɣer da. + %1$s yessireg inebgawen ad d-asen ɣer da. + Tbeddleḍ tansiwin n texxamt-a. + %1$s ibeddel tansiwin n texxamt-a. + Teffɣeḍ. Tamentilt: %1$s + %1$s yeffeɣ. Tamentilt: %2$s + %1$s yedda. Tamentilt: %2$s + Teddiḍ. Tamentilt: %1$s + Amtawi n tazwara: +\nAsader n yisefka… + Amtawi n tazwara: +\nAraju n tririt n uqeddac… + Taxxamt tilemt (tella %s) + + %1$s, %2$s, %3$s d %4$d-nniḍen + %1$s, %2$s, %3$s d %4$d n wiyaḍ + + %1$s, %2$s, %3$s d %4$s + %1$s, %2$s d %3$s + Tbeddleḍ asarag s tvidyut + Asarag s tvidyut yettwabeddel sɣur %1$s + Tesḥebseḍ asarag s tvidyut + %1$s yesseḥbes asarag s tvidyut + Tebdiḍ asarag s tvidyut + Asarag s tvidyut yebda-d sɣur %1$s + Tugiḍ tinnubga n %1$s + %1$s yugi tinnubga n %2$s + Tnecdeḍ-d %1$s + %1$s t•yenced-d %2$s + 🎉 Iqeddcen akk ttwagedlen seg uttekki! Taxxamt-a dayen ur tettuseqdac ara. + Ulac abeddel. + • Iqeddacen i yemsaḍan d %s ttwakksen seg tebdert n usireg. + • Iqeddacen i yemsaḍan d %s ttusirgen tura. + • Iqeddacen i yemsaḍan d %s ttwakksen seg tebdert n ugdal. + • Iqeddacen i yemsaḍan d %s ttwagedlen tura. + • Iqeddacen i yemsaḍan d %s ttusirgen. + • Iqeddacen i yemsaḍan d %s ttwagedlen. + Terriḍ iznan i d-itteddun ad d-ttbinen i %1$s + %1$s t•yerra iznan i d-itteddun ad d-ttbinen i %2$s + Teffɣeḍ seg texxamt + %1$s t•yeffeɣ seg texxamt + Teddiḍ-d ɣer texxamt + %1$s t•yedda-d ɣer texxamt + Tesnulfaḍ-d adiwenni + %1$s t•yesnulfa-d adiwenni \ No newline at end of file diff --git a/vector/src/main/res/values-lv/strings.xml b/vector/src/main/res/values-lv/strings.xml index b5226f43ce..ee98765a7c 100644 --- a/vector/src/main/res/values-lv/strings.xml +++ b/vector/src/main/res/values-lv/strings.xml @@ -246,13 +246,13 @@ Dalībnieka informācija Bijušie Lai notiek - Tomēr nē + Atcelt Saglabāt Pamest Nosūtīt Sūtīt atkārtoti Rediģēt - Citāts + Citēt Dalīties Vēlāk Pārsūtīt @@ -293,10 +293,10 @@ Izlase Cilvēki Istabas - Meklēt istabas - Meklēt favorītus - Meklēt cilvēkus - Meklēt istabas + Filtrēt istabu nosaukumus + Filtrēt izlasi + Filtrēt cilvēkus + Filtrēt istabu nosaukumus Uzaicinājumi Zema prioritāte Sarunas @@ -365,7 +365,7 @@ Ielādējas… Izslēgt Kopienas - Meklēt kopienas + Filtrēt kopienu nosaukumus Uzaicināt Kopienas Nav grupu @@ -378,8 +378,8 @@ Epasta adrese (izvēles) Tālruņa numurs Tālruņa numurs (izvēles) - Parole (atkārtoti) - Apstiprini savu jauno paroli + Parole atkārtoti + Apstipriniet savu jauno paroli Nepareizs lietotājvārds un/vai parole Lietotājvārdi var tikai saturēt burtus, ciparus, punktus, domuzīmes un apakšsvītras Parole par īsu (< 6 simboliem) @@ -388,17 +388,17 @@ Šķiet ievadīts nekorekts tālruņa numurs Šī epasta adrese jau tiek izmantota. Iztrūkst epasta adreses - Iztrūkst tālruņa # - Iztrūkst epasta adrese vai tālruņa # + Iztrūkst tālruņa numurs + Iztrūkst epasta adrese vai tālruņa numurs Nederīgs tokens Paroles nesakrīt - Aizmirsi paroli? + Aizmirsāt paroli\? Izmantot servera īpašus parametrus Pārbaudi epastu, lai turpinātu reģistrāciju Reģistrēšanās ar epastu un tālruņa numuru vienlaicīgi pagaidām netiek atbalstīta. Ar kontu būs saistīts vienīgi tālruņa numuru. \n \nSavu epastu varat pievienot profilam iestatījumos. - Bāzes serveris vēlas pārbaudīt, vai neesi robots + Bāzes serveris vēlas pārbaudīt, vai neesat robots Šāds lietotājvārds jau ir aizņemts Bāzes serveris: Identitāšu serveris: @@ -406,8 +406,8 @@ Lai atiestatītu paroli, ievadiet epasta adresi, kura piesaistīta kontam: Ir jābūt ievadītai kontam piesaistītajai epasta adresei. Jāievada jauna parole. - Epasts ir nosūtīts uz %s. Pēc tam, kad nospiedīsi uz tajā ietverto tīmekļa saiti, noklikšķini zemāk. - Neizdevās verificēt epasta adresi: pārbaudi vai esi noklikšķinājis(usi) uz saiti atsūtītajā epastā + Epasts ir nosūtīts uz %s. Kad nospiedīsiet uz tajā ietverto tīmekļa saiti, noklikšķiniet zemāk. + Neizdevās verificēt epasta adresi: pārbaudiet, vai esi noklikšķinājis(usi) uz saiti atsūtītajā epastā Jūsu parole ir atstatīta. \n \nJūs esat izrakstīts no visām sesijām un nesaņemsit push paziņojumus. Lai atkārtoti iespējotu paziņojumus, pierakstieties katrā savā ierīcē par jaunu. @@ -567,7 +567,7 @@ Lūdzu dod piekļuves atļauju nākamajā uznirstošajā logā, lai būtu iespē Iemesls ziņojumam par šo saturu Vai vēlaties paslēpt visas ziņas no šī lietotāja\? \n -\nŅemiet vērā, ka ar šo darbību tiks restartēta lietotne, un tas var aizņemt kādu laiku. +\nŅemiet vērā, ka ar šo darbību tiks pārstartēta lietotne, un tas var aizņemt kādu laiku. Atcelt augšupielādi Atcelt lejupielādi Meklēšana @@ -587,9 +587,9 @@ Lūdzu dod piekļuves atļauju nākamajā uznirstošajā logā, lai būtu iespē Izveidot istabu Ieiet istabā Ieiet istabā - Ievadiet istabas ID vai aliasi - Skatīt katalogu - Meklēju katalogā… + Ievadiet istabas ID vai aliasu + Pārlūkot katalogu + Meklē katalogā… Visas ziņas (ar skaņu) Visas ziņas Tikai pieminējumi @@ -637,14 +637,14 @@ Lūdzu dod piekļuves atļauju nākamajā uznirstošajā logā, lai būtu iespē Trešo pušu paziņojumi Autortiesības Privātuma politika - Iztīrīt ķešu - Iztīrīt mēdija ķešu + Iztīrīt kešatmiņu + Iztīrīt mediju kešatmiņu Turēt mēdiju (?) Lietotāja iestatījumi Paziņojumi Ignorētie dalībnieki Citi - Papildus + Papildu Kriptogrāfija Paziņojumus nosūtīt uz Lokālie kontakti @@ -653,7 +653,7 @@ Lūdzu dod piekļuves atļauju nākamajā uznirstošajā logā, lai būtu iespē Galvenais ekrāns Piestiprināt istabas ar garām palaistiem paziņojumiem Piestiprināt istabas ar neizlasītām ziņām - Ierīces + Sesijas Iespējot URL priekšskatu pēc noklusējuma Vienmēr rādīt ziņu laiku Rādīt ziņu laiku 12 stundu formātā (piem. 12:12pm) @@ -691,18 +691,18 @@ Lūdzu dod piekļuves atļauju nākamajā uznirstošajā logā, lai būtu iespē Parole nomainīta Rādīt visas ziņas no %s\? \n -\nŅemiet vērā, ka ar šo darbību tiks restartēta lietotne, un tas var aizņemt kādu laiku. +\nŅemiet vērā, ka ar šo darbību tiks pārstartēta lietotne, un tas var aizņemt kādu laiku. Vai tiešām vēlies turpmāk paziņojumus uz šo ierīci nesūtīt? Vai tiešām vēlies dzēst %1$s %2$s? Izvēlies valsti Valsts Lūdzu izvēlies valsti - Tālruņa # - Priekš šīs valsts nekorekts tālruņa # + Tālruņa numurs + Priekš šīs valsts nekorekts tālruņa numurs Tālruņa verifikācija Tika nosūtīts SMS ar aktivācijas kodu. Lūdzu ievadi šo kodu zemāk. Ievadi aktivācijas kodu - Tālruņa # validācija nesekmīga + Klūda tālruņa numura validācijā Kods Gaidas 3 dienas @@ -721,7 +721,7 @@ Lūdzu dod piekļuves atļauju nākamajā uznirstošajā logā, lai būtu iespē Iekļaut šo istabu katalogā Paziņojumi Piekļuve istabai - Piekļuve Istabas vēsturei + Piekļuve istabas vēsturei Kas var lasīt vēsturi? Kas var piekļūt šai istabai? Jebkurš @@ -730,10 +730,10 @@ Lūdzu dod piekļuves atļauju nākamajā uznirstošajā logā, lai būtu iespē Tikai dalībnieki (kopš tie pievienojušies) Lai ģenerētu saiti uz istabu, ir jābūt dotai adresei. Tikai uzaicinātie - Visi, kuri zin saiti uz istabu, izņemot viesus - Visi, kuri zin saiti uz istabu, ieskatot viesus + Visi, kuri zina saiti uz istabu, izņemot viesus + Visi, kuri zina saiti uz istabu, ieskatot viesus Lietotāji, kuriem liegta pieeja - Papildus + Papildu Šīs istabas iekšējais ID Adreses Izmēģinājumu lauciņš @@ -818,23 +818,23 @@ Lūdzu dod piekļuves atļauju nākamajā uznirstošajā logā, lai būtu iespē Eksportēt istabas atslēgas Eksportēt atslēgas vietējā failā Eksportēt - Ievadi paroli (paroles frāzi) - Apstiprināt paroli + Ievadiet frāzveida paroli + Apstiprināt frāzveida paroli Istabas šifrēšanas atslēgas tika salabātas \'%s\'. \n \nBrīdinājums: fails var tikt izdzēsts, ja lietotne tiek atinstalēta. Importēt E2E istabas atslēgas Importēt istabas atslēgas Importēt atslēgas no vietējā faila - Imports + Importēt Šifrēt vienīgi uz pārbaudītām ierīcēm Nekad nesūtīt šifrētas ziņas uz nepārbaudītām ierīcēm no šīs ierīces. Neverificēta Verificēta Melnajā sarakstā - nepazīstama ierīce + nepazīstama sesija nekā - Apstiprināt + Verificēt Apstiprinājumu atcelt Ietvert melnajā sarakstā Izņemt no melnā saraksta @@ -872,7 +872,7 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. %1$s iekš %2$s Meklēt vēsturē - Šrifta izmērs + Burtu izmērs Sīks Mazs Normāls @@ -959,7 +959,7 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Verificējiet visas savas sesijas, lai nodrošinātos, ka jūsu konts un ziņas ir drošībā Pārskatiet savas pierakstīšanās Nešifrēts - vai kāda cita Matrix lietotne ar cross-signing atbalstu + vai kādu citu Matrix lietotni ar cross-signing atbalstu Šis konts ir deaktivizēts. Šifrētas ziņas grupas čatos Šifrētas ziņas viens-pret-vienu čatos @@ -968,17 +968,17 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Ziņas šajā istabā ir nodrošinātas ar pilnīgu šifrēšanu. Šifrēšana iespējota Jauna pierakstīšanās. Vai tas bijāt jūs\? - Vai tiešām vēlies dzēst šo notikumu\? Ņem vērā, ka istabas nosaukuma vai tēmas nosaukuma maiņa var ietekmēt (atsaukt) izmaiņas. + Vai tiešām vēlaties dzēst šo notikumu\? Ņemiet vērā, ka istabas nosaukuma vai temata maiņa var atcelt izmaiņas. Apstipriniet dzēšanu - QA kods + QR kods Neuzticama Uzticama Sesijas Neizdevās iegūt sesijas Brīdinājums - Pārbaudīta + Verificēta Apstiprināt Verificējiet šo sesiju Jūsu servera administrators privātajās telpās un tiešajās ziņās pēc noklusējuma ir atspējojis pilnīgu šifrēšanu. @@ -1005,10 +1005,10 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Drošība Ziņas šeit ir nodrošinātas ar pilnīgu šifrēšanu. \n -\nJūsu ziņas tiek nodrošināti ar slēdzenēm, un tikai jums un saņēmējam ir unikālas atslēgas, lai tās atbloķētu. +\nJūsu ziņas tiek nodrošinātas ar slēdzenēm, un tikai jums un saņēmējam ir unikālas atslēgas, lai tās atbloķētu. Ziņas šeit ir nodrošinātas ar pilnīgu šifrēšanu. \n -\nJūsu ziņas tiek nodrošināti ar slēdzenēm, un tikai jums un saņēmējam ir unikālas atslēgas, lai tās atbloķētu. +\nJūsu ziņas tiek nodrošinātas ar slēdzenēm, un tikai jums un saņēmējam ir unikālas atslēgas, lai tās atbloķētu. Ziņas šeit nav nodrošinātas ar pilnīgu šifrēšanu. Ziņām šajā istabā netiek piemērota pilnīga šifrēšana. Jūs akceptējāt @@ -1028,7 +1028,7 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Parole Pierakstīties Pierakstīties - Matrix Id + Matrix ID Brīdinājums Šāds lietotājvārds jau ir aizņemts Tālāk @@ -1054,16 +1054,16 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Jūsu parole ir atiestatīta. Esmu verificējis(usi) savu epasta adresi Turpināt - Mainot paroli, tiks atiestatītas visas pilnīgas šifrēšanas atslēgas visās jūsu sesijās, padarot šifrēto tērzēšanas vēsturi neizlasāmu. Pirms paroles atiestatīšanas iestatiet atslēgu dublēšanu vai eksportējiet istabas atslēgas no citas sesijas. + Mainot paroli, tiks atiestatītas visas pilnīgas šifrēšanas atslēgas visās jūsu sesijās, padarot šifrētās sarakstes vēsturi neizlasāmu. Pirms paroles atiestatīšanas iestatiet atslēgu dublēšanu vai eksportējiet istabas atslēgas no citas sesijas. Uzmanību! Jauna parole Epasts Tālāk - Apstiprinājuma vēstule tiks nosūtīta uz tavu epasta adresi, lai apstiprinātu paroles nomaiņu. + Apstiprinājuma vēstule tiks nosūtīta uz jūsu epasta adresi, lai apstiprinātu paroles nomaiņu. Pierakstīties Reģistrēties Turpināt - Citi + Cits Iestatījumi Izslēgt skaņu Tikai pieminējumi @@ -1111,7 +1111,7 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Verificēts! Algoritms Versija - Droša reze + Droša rezerves kopija Vai tiešām to vēlaties\? Dalīties Gatavs @@ -1122,7 +1122,7 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Deaktivizēt kontu Nomaina jūsu parādāmo vārdu Padzen lietotāju ar norādīto id - Atstāt istabu + Atstāj istabu Uzaicina lietotāju ar norādīto id uz pašreizējo istabu Atceļ operatora statusu lietotājam ar norādīto Id Definē lietotāja statusu @@ -1149,7 +1149,7 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Deaktivizēt kontu Nodrošinieties pret piekļuves zaudēšanu šifrētām ziņām un datiem, dublējot šifrēšanas atslēgas savā serverī. Iestatīt drošu rezerves dublēšanu - Droša reze + Droša rezerves kopija Sūtīt paziņojumus par rakstīšanu Normāls Startēt pie ierīces ielādes @@ -1174,7 +1174,7 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Mainīt istabas avataru Apziņot visus Dzēst citu sūtītas ziņas - Pieejas liegumi lietotājiem + Liegt pieeju lietotājiem Padzīt lietotājus Mainīt iestatījumus Uzaicināt lietotājus @@ -1244,9 +1244,9 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. Verificēt ierīci Sistēmas noklusējuma Pielāgots (%1$d) iekš %2$s - Noklusējums iekš %1$s - Moderators iekš %1$s - Administrators iekš %1$s + %1$s noklusējums + %1$s moderators + %1$s administrators Pielāgots Moderators Administrators @@ -1261,4 +1261,516 @@ Nākotnē šī pārbaudes procedūra plānota sarežģītāka. %d sekundes %d sekundes + Šī sesija ir uzticama drošai ziņojumapmaiņai, jo %1$s (%2$s) to verificēja: + Verificējiet šo sesiju, lai atzīmētu to kā uzticamu un piešķirtu piekļuvi šifrētām ziņām. Ja neesat pierakstījies šajā sesijā, jūsu konts var būt kompromitēts: + Šī sesija ir uzticama drošai ziņojumapmaiņai, jo jūs to verificējāt: + Izrakstīties no šīs sesijas + Pārvaldīt sesijas + Parādīt visas sesijas + Aktīvās sesijas + Salīdziniet kodu ar to, kas parādīts otra lietotāja ekrānā. + Salīdziniet unikālās emocijzīmes, pārliecinoties tās ir vienādā secībā. + Lai būtu droši, dariet to klātienē vai izmantojiet citu komunikācijas veidu. + Lai būtu droši, verificējiet %s ar vienreizēja koda palīdzību. + Citas istabas + Nesenās istabas + Verifikācijas slēdziens + Bota pogas + Aptauja + Uzlīme + Kāds no uzskaitītajiem var būt kompromitēts: +\n +\n - jūsu bāzes serveris +\n - bāzes serveris, kuram pieslēdzies verificējamais lietotājs +\n - jūsu vai otra lietotāja interneta pieslēgums +\n - jūsu vai otra lietotāja ierīce + Nav droša + Meklējiet zaļo vairogu, lai pārliecinātos, ka lietotājs ir uzticams. Visiem lietotājiem istabā jābūt uzticamiem, lai nodrošinātu, ka istaba ir droša. + Tie nesakrīt + Tie sakrīt + Neuzticama pieteikšanās + Izveido istabu… + Dažas rakstzīmes nav atļautas + Rāda tikai pirmos rezultātus, ierakstiet vairāk burtus… + Citas sesijas + Pašreizējā sesija + Papildu iestatījumi + Apskatīt visas manas sesijas + Jūsu matrix.to saite ir nepareizi veidota + Dzēst datus + Dzēst datus + Dzēst visus datus + Vai + Nelasītas ziņas + Jūs padarījāt šo pieejamu tikai ar ielūgumiem. + %1$s padarīja šo pieejamu tikai ar ielūgumiem. + Jūs padarījāt istabu pieejamu tikai ar ielūgumiem. + %1$s padarīja istabu pieejamu tikai ar ielūgumiem. + Jūs padarījāt istabu publiski pieejamu visiem, kas zina saiti. + %1$s padarīja istabu publiski pieejamu visiem, kas zina saiti. + Jūs neignorējat nevienu lietotāju + Ignorēt lietotāju + Šobrīd nav tīkla savienojuma + ZIŅOT + Ziņot par šo saturu + Pielāgots ziņojums… + Nepiemērots saturs + Tas ir spams + Šajā istabā nav neviena faila + %1$d no %2$d + %s izlasīja + %1$s un %2$s izlasīja + %1$s, %2$s un %3$s izlasīja + Sūtīt pielikumu + Piekrītiet identitāšu servera (%s) pakalpojumu sniegšanas noteikumiem, lai padarītu sevi atrodamu citiem, izmantojot epasta adresi vai tālruņa numuru. + Verifikācijas kods nav pareizs. + Teksta ziņojums ir nosūtīts uz %s. Lūdzu, verifikācijas kodu no ziņojuma. + Neizdevās pieslēgties identitāšu serverim + Konfigurēt identitāšu serveri + Atvienot identitāšu serveri + Identitāšu serveris + Pārskatīt noteikumus + Pievienojas istabai… + Ieteikumi + Kontakti + Nesenie + Filtrēt pēc nosaukuma vai ID… + Sāciet rakstīt, lai parādītos rezultāti + Nekas nav atrasts, lietojiet Pievienot ar Matrix ID, lai meklētu serverī. + Izveido istabu… + Pievienot ar QR kodu + Pievienot ar Matrix ID + Saite nokopēta starpliktuvē + (rediģēts) + Fails %1$s ir lejupielādēts! + Lejupielādē failu %1$s… + Sūta failu (%1$s / %2$s) + Šifrē failu… + Sūta sīktēlu (%1$s / %2$s) + Šifrē sīktēlu… + Balss un video + Eksperts + Preferences + Jūs jau skataties šo istabu! + Citi trešo pušu paziņojumi + Matrix SDK versija + Šīs istabas priekšskatījums nav pieejams. Vai vēlaties tai pievienoties\? + Šī istaba šobrīd nav pieejama. +\nMēģiniet vēlreiz vēlāk vai lūdziet istabas administratoru pārbaudīt, vai jums ir piekļuve. + Lūdzu, gaidiet… + Mainīt + Pēdējo reiz rediģēja %1$s %2$s + Jums vairs nav nelasītu ziņu + Nosūtīja jums uzaicinājumu + Verifikācijas process beidzās dēļ noilguma + Lietotājs atcēla verifikāciju + %s vēlas verificēt jūsu sesiju + Verifikācijas pieprasījums + Verifikācija atcelta. +\nIemesls: %s + Otra puse atcēla verifikāciju. +\n%s + Pieprasījums atcelts + Jūs veiksmīgi verificējāt šo sesiju. + Gaida, kamēr partneris apstiprinās… + Apskatīt pieprasījumu + Jūs saņēmāt ienākošu verifikācijas pieprasījumu. + Nodrošinieties pret piekļuves zaudēšanu šifrētām ziņām un datiem + Tas biju es + Neparedzēta kļūda + Izveidot frāzveida paroli + %d+ + +%d + %1$s: %2$s + %1$s: + Tikai par kļūdām + Par ziņām un kļūdām + Vienmēr + Atvainotie, notikusi kļūda + Nospiediet šeit, lai redzētu vecākas ziņas + Šī istabas ir citas sarakstes turpinājums + Sarakste turpinās šeit + Šī istaba ir aizvietota un vairs nav aktīva + Pārskatīt tagad + Lai turpinātu izmantot %1$s bāzes serveri, jums ir jāpārskata un jāpiekrīt noteikumiem un nosacījumiem. + Kluss + Neverificēta sesija pieprasa šifrēšanas atslēgas. +\nSesijas nosaukums: %1$s +\nRedzēta pēdējo reizi: %2$s +\nJa neesat pierakstījies citā sesijā, ignorējiet šo pieprasījumu. + Jauna sesija pieprasa šifrēšanas atslēgas. +\nSesijas nosaukums: %1$s +\nRedzēta pēdējo reizi: %2$s +\nJa neesat pierakstījies citā sesijā, ignorējiet šo pieprasījumu. + Lai turpinātu, jums ir jāpieņem šī pakalpojuma noteikumi. + Sūtit balss ziņas + Pārvaldīt integrācijas + Parametrs nav derīgs. + Iztrūks obligāts parametrs. + %1$s: %2$s %3$s + %1$s: %2$s + ** Neizdevās nosūtīt - atveriet istabu + Es + Jauns uzaicinājums + Jaunas ziņas + Jauns notikums + %1$s un %2$s + %1$s iekš %2$s un %3$s + Rakstiet te… + Atslēgas ir veiksmīgi eksportētas + Lūdzu, izveidojiet frāzveida paroli eksportēto atslēgu šifrēšanai. Jums būs jaievada to pašu frāzveida paroli, lai varētu importēt atslēgas. + Istabas versija + Pievienot lokālo adresi + Iestatiet šis istabas adreses, lai lietotāji var atrast šo istabu uz jūsu bāzes servera (%1$s) + Dzēst adresi \"%1$s\"\? + Galvenā adrese + Skatiet un pārvaldiet šīs istabas adreses un tās redzamību istabu katalogā. + Istabas adreses + Atskaņot aizvara skaņu + Izvēlēties + Izvēlēties + Noklusējuma kompresija + Papildinformācija: %s + Verificējot jūsu tālruņa numuru, radās kļūda. + Verificējot jūsu epasta adresi, radās kļūda. + Lai to izdarītu, iespējojiet ‘Atļaut integrācijas’ iestatījumos. + Datu plāna taupīšanas režīms izmanto filtru, lai klātbūtnes atjauninājumi un rakstīšanas paziņojumi tiek izfiltrēti. + Jā, es vēlos palīdzēt! + Priekšskatīt saites sarakstē, kad jūsu bāzes serveris atbalsta šo iespēju. + Integrācijas + Neizdevās atjaunināt iestatījumus. + Atvērt iestatījumus + Atjaunināt istabu + Sūtīt m.room.server_acl notikumus + Jums nav atļaujas atjaunināt lomas, kas nepieciešamas, lai mainītu dažādas istabas daļas + Skatiet un atjauniniet lomas, kas nepieciešamas, lai mainītu dažādas istabas daļas. + Atceļot pieejas liegumu, lietotājam atkal būs iespēja pievienoties istabai. + Atcelt pieejas liegumu lietotājam + Pieejas lieguma iemesls + Liegt pieeju lietotājam + lietotāja padzīšanas gadījumā tas tiks dzēsts no šis istabas. +\n +\nLai novērstu atkārtotu pievienošanos, tā vietā jums vajadzētu liegt pieeju. + Padzīšanas iemesls + Padzīt lietotāju + Vai tiešām vēlaties atcelt uzaicinājumu šim lietotājam\? + Atceļot ši lietotāja ignorēšanu, visas lietotāja ziņas atkal būs redzamas. + Ignorējot šo lietotāju noņems viņa ziņas istabās, kuras ir jums kopīgas. +\n +\nJūs varat atcelt šo darbību jebkurā brīdī vispārīgajos iestatījumos. + Pazemināt + Pazemināt sevi\? + Zvani + Pieprasījums nosūtīts + Atkārtoti pieprasīt šifrēšanas atslēgas no citām jūsu sesijām. + Šis tālruņa numurs jau ir definēts. + Sūtīt uzlīmi + Bezvadu austiņas + Austiņas + Skaļrunis + Istabu katalogs + Identitāšu serveris nav sakonfigurēts. + Jaunā vērtība + Atgriezties + Pārslēgt + Jūs nevarat uzsākt zvanu ar sevi, pagaidiet, kamēr dalībnieki akceptēs uzaicinājumu + Jūs nevarat uzsākt zvanu ar sevi + Trešo pušu licences + Sūtīt uzlīmi + Sakarā ar pilnīgu šifrēšanu, jums var būt nepieciešams sagaidīt ziņu no kāda, jo šifrēšanas atslēgas netika pareizi nosūtītas jums. + Gaida šo ziņu, tas var aizņemt ilgāku laiku + Jums nav piekļuves šai ziņai + + Parādīt ierīci, ar kuru jūs šobrīd varat veikt verifikāciju + Parādīt %d ierīces, ar kurām jūs šobrīd varat veikt verifikāciju + Parādīt %d ierīces, ar kurām jūs šobrīd varat veikt verifikāciju + + Jums būs jāatsāk bez vēstures, ziņām, uzticamām ierīcēm un uzticamiem lietotājiem + Ja jūs atiestatīsiet visu + Veiciet atiestatīšanu tikai tad, ja jums vairs nav nevienas citas ierīces, ar kuru verificēt šo ierīci. + Pilna atiestatīšana + Aizmirsāt vai pazaudējāt visas atkopšanās iespējas\? Atiestatiet visu + Izmantojiet savu %1$s vai savu %2$s, lai turpinātu. + Izmantojiet jaunāko ${app_name} citās savās ierīcēs: + Izmantojiet jaunāko ${app_name} citās savās ierīcēs, ${app_name} Web, ${app_name} Desktop, ${app_name} iOS, ${app_name} Android, vai kādu citu Matrix lietotni ar cross-signing atbalstu + Iestatiet jaunu konta paroli… + Šis ir sākums jūsu tiešās sarakstes vēsturei ar %s. + Šis ir šīs sarakstes pats sākums. + Šis ir pats %s sākums. + Jūs pievienojāties. + %s pievienojās. + Jūs izveidojāt un sakonfigurējāt istabu. + %s izveidoja un sakonfigurēja istabu. + Šajā istabā izmantotā šifrēšana netiek atbalstīta + Ziņas šajā istabā ir aizsargātas ar pilnīgu šifrēšanu. Uzziniet vairāk un verificējiet lietotājus viņu profilā. + Verifikācija atcelta + Jūsu konts varētu būt kompromitēts + Tas nebiju es + Izmantojiet šo sesiju jaunās sesijas verifikācijai, tādējādi dodot piekļuvi šifrētām ziņām. + Pieskarieties, lai pārskatītu un verificētu + Rediģēšanas iemesls + Iekļaut iemeslu + Dzēst… + Ja jūs nevarat piekļūt esošai sesijai + Izmantot atkopšanās frāzveida paroli vai atslēgu + Izmantojiet citu sesiju šis sesijas verifikācijai, tādējādi dodot piekļuvi šifrētām ziņām. + Citi lietotāji var tai neuzticēties + Iespējot šifrēšanu + Pēc iespējošanas šifrēšana istabai nevar tikt izslēgta. Šifrētā istabā sūtītas ziņas nav redzamas serverim, tikai istabas dalībniekiem. Šifrēšanas iespējošana var neļaut pareizi strādāt daudzus botiem un tiltiem. + Jums nav atļaujas, lai iespējotu šifrēšanu šajā istabā. + + Viena persona + %1$d cilvēki + %1$d cilvēki + + Papildu drošībai, verificējiet %s, pārbaudot vienreizēju kodu uz jūsu abu ierīcēm. +\n +\nMaksimālai drošībai, dariet to klātienē. + Gaida %s… + Verificēts %s + Verificē %s + QR koda attēls + Ja nevarat noskenēt kodu, veiciet verifikāciju, izmantojot unikālu emocijzīmju salīdzināšanu. + Verificēt ar emocijzīmēm + Verifikācija, izmantojot emocijzīmju salīdzināšanu + Ja jūs neatrodaties klātienē, salīdziniet emocijzīmes + Nevar skenēt + Skenēt viņu kodu + Skenējiet kodu ar otra lietotāja ierīci, lai droši verificētu viens otru + Jūs + Verificēt manuāli + Verificējiet šo sesiju + Verifikācijas pieprasījums + Verifikācija nosūtīta + %s akceptēja + Jūs atcēlāt + %s atcēla + Gaida… + Reaģēja ar %s + Bloķēt pievienošanos šai istabai ikvienam, kas nav daļa no %s + Iespējot šifrēšanu + Sākotnējā sinhronizācija… + Jūs izrakstījāties + Pierakstīties vēlreiz + Jūs izrakstījāties + Neizdevās atrast derīgu bāzes serveri. Lūdzu, pārbaudiet savu identifikatoru + Šis nav pareizs identifikators. Sagaidāmais formāts: \'@user:homeserver.org\' + Ja jūs nezināt savu paroli, dodieties atpakaļ, lai to atiestatītu. + Ja izveidojat kontu bāzes serverī, izmantojiet savu Matrix ID (paraugs: @user:domain.com) un paroli zemāk. + Pierakstīties ar Matrix ID + Pierakstīties ar Matrix ID + Alternatīvi, ja jums jau ir konts, un jūs zināt savu Matrix identifikatoru un paroli, varat izmantot šo metodi: + Uz šī bāzes servera darbojas pārāk veca versija. Aiciniet sava bāzes servera administratoru veikt atjauninājumus. Jūs varat turpināt, tomēr atsevišķas iespējas var nedarboties pareizi. + Uz šī bāzes servera darbojas pārāk veca versija, lai izveidotu savienojumu ar to. Aiciniet sava bāzes servera administratoru veikt atjaunināšanu. + Novecojis bāzes serveris + Ievadītais kods nav pareizs. Lūdzu, pārbaudiet. + Mēs tikko nosūtījām epastu uz %1$s. +\nLūdzu, noklikšķiniet uz saites epastā, lai turpinātu konta izveidi. + Lūdzu, pārbaudiet savu epastu + Pieņemt noteikumus, lai turpinātu + Lūdzu, veiciet CAPTCHA izaicinājumu + Izvēlēties pielāgotu bāzes serveri + Izvēlēties Element Matrix Services + Izvēlēties matrix.org + Jūsu konts vēl nav izveidots.. +\n +\nVai pārtraukt reģistrēšanos\? + Lūdzu, izmantojiet starptautisko formātu. + Iestatiet tālruņa numuru,lai pēc izvēles ļaut jums zināmiem cilvēkiem atrast. + Iestatiet tālruņa numuru + Tālāk + Epasts (izvēles) + Iestatiet epastu, lai atgūtu savu kontu. Vēlāk jūs varat pēc izvēles ļaut jums zināmiem cilvēkiem atrast sevi pēc epasta adreses. + Iestatīt epasta adresi + Jūsu parole vēl nav nomainīta. +\n +\nVai pārtraukt paroles nomaiņu\? + Gatavs! + Nospiediet saiti, lai apstiprinātu savu jauno paroli. Kad esat sekojis saitei, noklikšķiniet zemāk. + Apstiprinājuma epasts tika nosūtīts uz %1$s. + Pārbaudiet ienākošos epastus + Šis epasts nav piesaistīts nevienam kontam + Atiestatīt paroli uz %1$s + Šis epasts nav piesaistīts nevienam kontam. + Lietotnei neizdodas izveidot kontu uz šī bāzes servera. +\n +\nVai vēlaties reģistrēties, izmantojot tīmekļa klientu\? + Atvainojiet, šis serveris nepieņem jaunus kontus. + Lietotnei neizdodas pierakstīties uz šī bāzes servera. Bāzes serveris atbalsta sekojošos pierakstīšanās veidus: %1$s. +\n +\nVai vēlaties pierakstīties, izmantojot tīmekļa klientu\? + Radās kļūda, ielādējot lapu: %1$s (%2$d) + Ievadiet servera adresi, kuru vēlaties izmantot + Ievadiet Modular Element vai servera adresi, kuru vēlaties izmantot + Premium hostings organizācijām + Adrese + Element Matrix Services adrese + Attīrīt vēsturi + Turpināt, izmantojot SSO + Pierakstīties uz %1$s + Pieslēgšanās pielāgotam serverim + Pieslēgšanās Element Matrix Services + Pieslēgšanās %1$s + vienotā pierakstīšanās + Pierakstīties ar %s + Reģistrēties ar %s + Turpināt ar %s + Pielāgoti un papildu iestatījumi + Uzzināt vairāk + Premium hostings organizācijām + Pievienojieties bez maksas miljoniem lietotāju lielākajā publiskajā serverī + Tāpat kā ar epastu, kontiem ir sava mājvieta, lai gan jūs varat sazināties ar jebkuru citu + Izvēlieties serveri + Uzsākt + Paplašiniet un pielāgojiet savam ērtumam + Paturiet saraksti privātu ar šifrēšanas palīdzību + Sarakstieties ar cilvēkiem pa tiešo vai grupās + Tā ir jūsu sarakste. Tā pieder jums. + Paturiet ilgāk uz istabas, lai redzētu vairāk iespēju + Jūs neveicāt nekādas izmaiņas + %1$s neveica nekādas izmaiņas + Istabas iestatījumi + Pamest istabu + Izņemt no zemas prioritātes saraksta + Pievienot zemas prioritātes sarakstam + Izņemt no izlases + Pievienot izlasei + Uzlīme + Galerija + Audio + Kontakts + Fails + Pievienot attēlu no + QR kods + Nosaukums vai ID (#piemers:matrix.org) + Atvērt istabu katalogu + Sūtīt jaunu tiešo ziņu + Izveidot jaunu istabu + Nevarat atrast meklēto\? + Filtrēt sarakstes… + Istaba ir izveidota, bet daži ielūgumi nav nosūtīti šāda iemesla dēļ: +\n +\n%s + Pievienot šo istabu istabu katalogam + Jebkurš varēs pievienoties istabai + Izveidot jaunu istabu + Jūsu istabas parādīsies šeit. Pieskarieties + labajā apakšējā stūrī, lai atrastu esošās istabas vai izveidotu jaunu. + Atkārtot + Jūs neizmantojat nevienu identitāšu serveri + Rezerves kopiju nevarēja atšifrēt ar šo atkopšanās atslēgu: lūdzu, pārbaudiet, vai ievadījāt pareizo atkopšanās atslēgu. + Kalkulē atkopšanās atslēgu… + Frāzveida parole pārāk vāja + Nosūta doto ziņu ar sniegu + Nosūta doto ziņu ar konfeti + Atbastīta tikai šifrētās istabās + Nosūta ziņu vienkāršā tekstā, nepielietojot markdown + Izveido vienkāršu aptauju + Nosūta doto ziņu uzsvērti izkrāsotu varavīksnes krāsās + Nosūta doto ziņu izkrāsotu varavīksnes krāsās + Pievieno ¯\\_(ツ)_/¯ vienkārša teksta ziņas sākumā + Iespējo/atspējo markdown + Iestata istabas tematu + Pievienojas istabai ar norādīto aliasu + Atceļ pieejas liegumu lietotājam ar norādīto id + Komandai \"%s\" nepieciešami vairāk parametri vai arī kāds no parametriem ir nepareizs. + Vai tiešām vēlaties dzēst visas nenosūtītas ziņas šajā istabā\? + Dzēst nenosūtītās ziņas + Ziņas neizdevās nosūtīt + Vai vēlaties atcelt ziņu nosūtīšanu\? + Nosūtīta + Nosūta + Neizdevās autentificēties + Atmest izmaiņas + Te ir nesaglabātas izmaiņas. Atmest izmaiņas\? + Saite bija nepareizi veidota + QR kods nav noskenēts! + Nederīgs QR kods (nederīgs URI)! + Nevar atrast šo istabu. Pārliecinieties, ka tāda eksistē. + Brīdinājums! Pēdējais atlikušais mēģinājums pirms izrakstīšanās! + Atsaukt uzaicinājumu uz %1$s\? + Atsaukt uzaicinājumu + Meklēt kontaktus Matrix + Ielasa jūsu kontaktus… + Meklēt manos kontaktos + Telefongrāmata + Pievienot no manas telefongrāmatas + UZZINĀT VAIRĀK + SAPRATU + Esam priecīgi paziņot, ka mēs esam mainījuši nosaukumu! Jūsu lietotne ir atjaunināta un esat pierakstījies savā kontā. + Riot tagad saucas Element! + Gaida šifrēšanas vēsturi + Jūs veiksmīgi nomainījāt istabas iestatījumus + Loma + Iestatīt lomu + Atvienoties no identitāšu servera %s\? + Atvērt %s noteikumus + Dalieties ar šo kodu, lai cilvēki varētu noskenēt to, konta pievienošanai un sarakstes uzsākšanai. + Mans kods + Dalīties ar manu kodu + Skenēt QR kodu + Tas nav derīgs matrix QR kods + Uzaicinājums nosūtīts uz %1$s + Uzaicina lietotājus… + UZAICINĀT + Pievienot cilvēkus + Pievienot dalībniekus + Saite %1$s ved uz citu vietni: %2$s. +\n +\nVai tiešām vēlaties turpināt\? + Pārbaudiet šo saiti + Atzīmēt kā uzticamu + Interaktīvi verificēt ar emocijzīmēm + Verificējiet sesiju + Verificējiet jauno pierakstīšanos no sava konta: %1$s + Šifrēts ar neverificētu sesiju + sūta sniegu ❄️ + sūta konfeti 🎉 + %1$s (%2$s) + Ievadiet %s + Pieejams šifrēšanas atjauninājums + Ziņa… + Nepareizs lietotājvārds un/vai parole. Ievadītā parole sākas vai beidzas ar atstarpēm. Lūdzu, pārbaudiet to. + Gaida uz %s… + Gandrīz galā! Gaida apstiprinājumu… + Gandrīz galā! Vai otrā ierīcē redzams tas pats vairogs\? + "Temats: " + Pievienot tematu + Pabeigt + Ievadiet savu %s, lai turpinātu. + Apstiprināt %s + Konta parole + Verificējiet savas ierīces no iestatījumiem. + Kāds no uzskaitītajiem var būt kompromitēts: +\n +\n- jūsu parole +\n- jūsu bāzes serveris +\n- šī vai cita ierīce +\n- interneta savienojums kādai no ierīcēm +\n +\nMēs iesakām jums nekavējoties nomainīt paroli un atiestatīšanas atslēgu iestatījumos. + Atsvaidzināt + Vai vēlaties sūtīt šo pielikumu uz %1$s\? + Brīdinājums: + Jauna pierakstīšanās + Konta dati + Lidmašīnas režīms ir ieslēgts + Savienojums ar serveri ir zaudēts + Gandrīz galā! Vai %s redzams tas pats vairogs\? + Kamēr šis lietotājs nav padarījis šo sesiju uzticamu, ziņas uz un no tās ir marķētas ar brīdinājumiem. Alternatīvi, jūs varat manuāli verificēt šos sesiju. + %1$s (%2$s) pierakstījās, izmantojot jaunu sesiju: + Lūdzu, ievadiet frāzveida paroli + Frāzveida paroles nesakrīt + Piekļuve istabai + Pārvaldiet epasta adreses un tālruņu numurus, kas saistīti ar jūsu Matrix kontu + Epasti un tālruņa numuri + Paroles nesakrīt + Parole nav derīga + Atjaunināt paroli + Integrācija pārvaldnieks + Atļaut integrācijas + Deaktivizēt manu kontu + Sākotnējā sinhronizācija: +\nLejupielādē datus… + Sākotnējā sinhronizācija: +\nGaida servera atbildi… \ No newline at end of file diff --git a/vector/src/main/res/values-ml/strings.xml b/vector/src/main/res/values-ml/strings.xml index 867f5661c2..e11aaa7639 100644 --- a/vector/src/main/res/values-ml/strings.xml +++ b/vector/src/main/res/values-ml/strings.xml @@ -412,4 +412,232 @@ പുറത്തിറങ്ങുക പുറത്തിറങ്ങുക കീ ബാക്കപ്പ് ഉപയൊഗിക്കൂ + %1$s ഒരു സ്റ്റിക്കർ അയച്ചു. + നിങ്ങൾ ഒരു ചിത്രം അയച്ചു. + %1$s ഒരു ചിത്രം അയച്ചു. + %1$s: %2$s + നിങ്ങൾ %1$s-നെ(യെ) പുറത്താക്കി + %1$s %2$s-നെ(യെ) പുറത്താക്കി + നിങ്ങൾ ചേർന്നു + %1$s ചേർന്നു + നിങ്ങൾ മുറിയിൽ ചേർന്നു + %1$s മുറിയിൽ ചേർന്നു + %1$s നിങ്ങളെ ക്ഷണിച്ചു + നിങ്ങൾ %1$s-നെ(യെ) ക്ഷണിച്ചു + %1$s %2$s-നെ(യെ) ക്ഷണിച്ചു + നിങ്ങൾ മുറി സൃഷ്ടിച്ചു + %1$s മുറി സൃഷ്ടിച്ചു + നിങ്ങളുടെ ക്ഷണം + %s-ന്റെ ക്ഷണം + നിങ്ങൾ ഒരു സ്റ്റിക്കർ അയച്ചു. + %1$s സന്ദേശം നീക്കം ചെയ്തു [കാരണം: %2$s] + സന്ദേശം നീക്കം ചെയ്തു [കാരണം: %1$s] + സന്ദേശം %1$s നീക്കംചെയ്തു + സന്ദേശം നീക്കംചെയ്‌തു + നിങ്ങൾ മുറിയുടെ അവതാർ നീക്കംചെയ്‌തു + %1$s മുറിയുടെ അവതാർ നീക്കം ചെയ്‌തു + നിങ്ങൾ മുറിയുടെ വിഷയം നീക്കംചെയ്‌തു + %1$s മുറിയുടെ വിഷയം നീക്കം ചെയ്‌തു + നിങ്ങൾ മുറിയുടെ പേര് നീക്കംചെയ്‌തു + %1$s മുറിയുടെ പേര് നീക്കംചെയ്‌തു + (അവതാറും മാറ്റി) + VoIP കോൺഫറൻസ് പൂർത്തിയായി + VoIP കോൺഫറൻസ് ആരംഭിച്ചു + നിങ്ങൾ ഒരു VoIP കൊൺഫറൻസ് അഭ്യർത്ഥിച്ചു + %1$s ഒരു VoIP കോൺഫറൻസ് അഭ്യർത്ഥിച്ചു + 🎉 എല്ലാ സെർവറുകളും പങ്കെടുക്കുന്നതിൽ നിന്ന് വിലക്കി! ഈ മുറി ഇനി ഉപയോഗിക്കാനാവില്ല. + മാറ്റമൊന്നുമില്ല. + • ഐപി ലിറ്ററലുകളുമായി പൊരുത്തപ്പെടുന്ന സെർവർ ഇപ്പോൾ നിരോധിച്ചു. + • ഐപി ലിറ്ററലുകളുമായി പൊരുത്തപ്പെടുന്ന സെർവർ ഇപ്പോൾ അനുവദനീയമാണ്. + • അനുവദനീയ പട്ടികയിൽ നിന്നും %sമായി പൊരുത്തപ്പെടുന്ന സെർവർ നീക്കം ചെയ്തു. + • %sമായി പൊരുത്തപ്പെടുന്ന സെർവർ ഇപ്പോൾ അനുവദനീയമാണ്. + • %sമായി പൊരുത്തപ്പെടുന്ന സെർവർ നിരോധന പട്ടികയിൽ നിന്ന് നീക്കംചെയ്‌തു. + • %s മായി പൊരുത്തപ്പെടുന്ന സെർവർ ഇപ്പോൾ നിരോധിച്ചിരിക്കുന്നു. + ഈ റൂമിനായി നിങ്ങൾ സെർവർ ACL-കൾ മാറ്റി. + %s ഈ മുറിക്കായി സെർവർ ACL-കൾ മാറ്റി. + • ഐപി ലിറ്ററലുകളുമായി പൊരുത്തപ്പെടുന്ന സെർവർ അനുവദനീയമാണ്. + • ഐപി ലിറ്ററലുകളുമായി പൊരുത്തപ്പെടുന്ന സെർവർ നിരോധിച്ചിരിക്കുന്നു. + • %s മായി പൊരുത്തപ്പെടുന്ന സെർവർ നിരോധിച്ചിരിക്കുന്നു. + • %s മായി പൊരുത്തപ്പെടുന്ന സെർവർ അനുവദനീയമാണ്. + ഈ മുറിക്കായി നിങ്ങൾ സെർവർ ACL-കൾ സജ്ജമാക്കി. + %s ഈ മുറിക്കായി സെർവർ ACL-കൾ സജ്ജമാക്കി. + നിങ്ങൾ ഇവിടെ നവീകരിച്ചു. + %s ഇവിടെ നവീകരിച്ചു. + നിങ്ങൾ ഈ മുറി നവീകരിച്ചു. + %s ഈ മുറി നവീകരിച്ചു. + നിങ്ങൾ എൻഡ്-ടു-എൻഡ് എൻ‌ക്രിപ്ഷൻ ഓണാക്കി (%1$s) + %1$s എൻഡ്-ടു-എൻഡ് എൻ‌ക്രിപ്ഷൻ ഓണാക്കി (%2$s) + അജ്ഞാതം (%s). + ആർക്കും. + എല്ലാ മുറി അംഗങ്ങളും. + എല്ലാ മുറി അംഗങ്ങളും, അവർ ചേർന്ന സമയം മുതൽ. + എല്ലാ മുറി അംഗങ്ങളും, അവരെ ക്ഷണിച്ച സമയം മുതൽ. + ഭാവിയിലെ സന്ദേശങ്ങൾ %1$s ന് നിങ്ങൾ ദൃശ്യമാക്കി + %1$s ഭാവി സന്ദേശങ്ങൾ %2$s ന് ദൃശ്യമാക്കി + നിങ്ങൾ ഭാവിയിലെ മുറിയുടെ ചരിത്രം %1$s ന് ദൃശ്യമാക്കി + %1$s ഭാവിയിലെ മുറിയുടെ ചരിത്രം %2$s ന് ദൃശ്യമാക്കി + നിങ്ങൾ കോൾ അവസാനിപ്പിച്ചു. + %s കോൾ അവസാനിപ്പിച്ചു. + നിങ്ങൾ കോളിന് മറുപടി നൽകി. + %s കോളിന് മറുപടി നൽകി. + കോൾ സജ്ജീകരിക്കുന്നതിന് നിങ്ങൾ ഡാറ്റ അയച്ചു. + കോൾ സജ്ജീകരിക്കുന്നതിന് %s ഡാറ്റ അയച്ചു. + നിങ്ങൾ ഒരു വോയ്സ് കോൾ നടത്തി. + %s ഒരു വോയ്സ് കോൾ നടത്തി. + നിങ്ങൾ ഒരു വീഡിയോ കോൾ നടത്തി. + %s ഒരു വീഡിയോ കോൾ നടത്തി. + നിങ്ങൾ മുറിയുടെ പേര് ഇതിലേക്ക് മാറ്റി: %1$s + %1$s മുറിയുടെ പേര് ഇതിലേക്ക് മാറ്റി: %2$s + നിങ്ങൾ മുറിയുടെ അവതാർ മാറ്റി + %1$s മുറിയുടെ അവതാർ മാറ്റി + നിങ്ങൾ വിഷയം ഇതിലേക്ക് മാറ്റി: %1$s + %1$s വിഷയം ഇതിലേക്ക് മാറ്റി: %2$s + നിങ്ങൾ നിങ്ങളുടെ പ്രദർശന നാമം നീക്കം ചെയ്തു (ഇത് %1$s ആയിരുന്നു) + %1$s അവരുടെ പ്രദർശന നാമം നീക്കം ചെയ്തു (ഇത് %2$s ആയിരുന്നു) + നിങ്ങൾ നിങ്ങളുടെ പ്രദർശന നാമം %1$sൽ നിന്നും %2$s ലേക്ക് മാറ്റി + %1$s അവരുടെ പ്രദർശന നാമം %2$s ൽ നിന്നും %3$s ആക്കി മാറ്റി + നിങ്ങൾ നിങ്ങളുടെ പ്രദർശന നാമം %1$s ആയി സജ്ജമാക്കി + %1$s അവരുടെ പ്രദർശന നാമം %2$s ആയി സജ്ജമാക്കി + നിങ്ങൾ നിങ്ങളുടെ അവതാർ മാറ്റി + %1$s അവരുടെ അവതാർ മാറ്റി + നിങ്ങൾ %1$s ന്റെ ക്ഷണം പിൻവലിച്ചു + %1$s %2$s ന്റെ ക്ഷണം പിൻവലിച്ചു + നിങ്ങൾ %1$s നെ നിരോധിച്ചു + %1$s %2$s നെ നിരോധിച്ചു + നിങ്ങൾ %1$s ന്റെ നിരോധനം മാറ്റി + %1$s %2$s ന്റെ നിരോധനം മാറ്റി + നിങ്ങൾ ക്ഷണം നിരസിച്ചു + %1$s ക്ഷണം നിരസിച്ചു + നിങ്ങൾ മുറി വിട്ടു + %1$s മുറി വിട്ടു + നിങ്ങൾ മുറി വിട്ടു + %1$s മുറി വിട്ടു + നിങ്ങൾ ചർച്ച സൃഷ്ടിച്ചു + %1$s ചർച്ച സൃഷ്ടിച്ചു + %1$s ഈ മുറിയുടെ പ്രധാന വിലാസമായി %2$s സജ്ജമാക്കി. + നിങ്ങൾ %1$s ചേർത്ത് ഈ മുറിയുടെ വിലാസങ്ങളായിരുന്ന %2$s നീക്കം ചെയ്‌തു. + %1$s %2$s ചേർത്ത് ഈ മുറിയുടെ വിലാസങ്ങളായിരുന്ന %3$s നീക്കം ചെയ്‌തു. + + നിങ്ങൾ ഈ മുറിയുടെ വിലാസമായിരുന്ന %1$s നീക്കംചെയ്‌തു. + നിങ്ങൾ ഈ മുറിയുടെ വിലാസങ്ങളായിരുന്ന %1$s നീക്കംചെയ്‌തു. + + + %1$s ഈ മുറിയുടെ വിലാസമായിരുന്ന %2$s നീക്കംചെയ്‌തു. + %1$s ഈ മുറിയുടെ വിലാസങ്ങളായിരുന്ന %2$s നീക്കംചെയ്‌തു. + + + ഈ മുറിയുടെ വിലാസമായി നിങ്ങൾ %1$s ചേർത്തു. + ഈ മുറിയുടെ വിലാസങ്ങളായി നിങ്ങൾ %1$s ചേർത്തു. + + + ഈ മുറിയുടെ വിലാസമായി %1$s %2$s ചേർത്തു. + ഈ മുറിയുടെ വിലാസങ്ങളായി %1$s %2$s ചേർത്തു. + + %1$sന്റെ ക്ഷണം നിങ്ങൾ പിൻവലിച്ചു. കാരണം: %2$s + %1$s %2$sന്റെ ക്ഷണം പിൻവലിച്ചു. കാരണം: %3$s + %1$sനുള്ള ക്ഷണം നിങ്ങൾ സ്വീകരിച്ചു. കാരണം: %2$s + %1$s %2$sനുള്ള ക്ഷണം സ്വീകരിച്ചു. കാരണം: %3$s + %1$sന് മുറിയിൽ ചേരുന്നതിനുള്ള ക്ഷണം നിങ്ങൾ റദ്ദാക്കി. കാരണം: %2$s + %2$sന് മുറിയിൽ ചേരുന്നതിനുള്ള ക്ഷണം %1$s റദ്ദാക്കി. കാരണം: %3$s + മുറിയിൽ ചേരാൻ നിങ്ങൾ %1$sന് ഒരു ക്ഷണം അയച്ചു. കാരണം: %2$s + %1$s മുറിയിൽ ചേരാൻ %2$sന്ക്ഷണം അയച്ചു. കാരണം:%3$s + നിങ്ങൾ %1$sനെ വിലക്കി. കാരണം: %2$s + %1$s %2$s ന്റെ വിലക്ക് നീക്കി. കാരണം: %3$s + നിങ്ങൾ %1$sന്റെ വിലക്ക് നീക്കി. കാരണം: %2$s + %1$s %2$sന്റെ വിലക്ക് നീക്കി. കാരണം: %3$s + നിങ്ങൾ %1$sനെ പുറത്താക്കി. കാരണം:%2$s + %1$s %2$sനെ പുറത്താക്കി. കാരണം: %3$s + നിങ്ങൾ ക്ഷണം നിരസിച്ചു. കാരണം: %1$s + %1$s ക്ഷണം നിരസിച്ചു. കാരണം: %2$s + നിങ്ങൾ ഉപേക്ഷിച്ചു. കാരണം: %1$s + %1$s ഉപേക്ഷിച്ചു. കാരണം: %2$s + നിങ്ങൾ മുറി വിട്ടു. കാരണം: %1$s + %1$s മുറി വിട്ടു. കാരണം: %2$s + നിങ്ങൾ ചേർന്നു. കാരണം: %1$s + %1$s ചേർന്നു. കാരണം: %2$s + നിങ്ങൾ മുറിയിൽ ചേർന്നു. കാരണം: %1$s + %1$s മുറിയിൽ ചേർന്നു. കാരണം: %2$s + %1$s നിങ്ങളെ ക്ഷണിച്ചു. കാരണം: %2$s + നിങ്ങൾ %1$sനെ ക്ഷണിച്ചു. കാരണം: %2$s + %1$s %2$sനെ ക്ഷണിച്ചു. കാരണം: %3$s + നിങ്ങളുടെ ക്ഷണം. കാരണം: %1$s + %1$s ന്റെ ക്ഷണം. കാരണം: %2$s + അയയ്‌ക്കാനുള്ള ശ്രേണി മായ്‌ക്കുക + സന്ദേശം അയയ്ക്കുന്നു… + പ്രാരംഭ സമന്വയം: +\nഅക്കൗണ്ട് ഡാറ്റ ഇറക്കുമതി ചെയ്യുന്നു + പ്രാരംഭ സമന്വയം: +\nജനസമൂഹങ്ങൾ ഇറക്കുമതി ചെയ്യുന്നു + പ്രാരംഭ സമന്വയം: +\nഉപേക്ഷിച്ച മുറികൾ ഇറക്കുമതി ചെയ്യുന്നു + പ്രാരംഭ സമന്വയം: +\nക്ഷണിക്കപ്പെട്ട മുറികൾ ഇറക്കുമതി ചെയ്യുന്നു + പ്രാരംഭ സമന്വയം: +\nചേർന്ന മുറികൾ ഇറക്കുമതി ചെയ്യുന്നു + പ്രാരംഭ സമന്വയം: +\nമുറികൾ ഇറക്കുമതി ചെയ്യുന്നു + പ്രാരംഭ സമന്വയം: +\nക്രിപ്‌റ്റോ ഇറക്കുമതി ചെയ്യുന്നു + പ്രാരംഭ സമന്വയം: +\nഅക്കൗണ്ട് ഇറക്കുമതി ചെയ്യുന്നു… + പ്രാരംഭ സമന്വയം: +\nഡാറ്റ ഡൗൺലോഡുചെയ്യുന്നു… + പ്രാരംഭ സമന്വയം: +\nസെർവർ പ്രതികരണത്തിനായി കാത്തിരിക്കുന്നു… + ശൂന്യമായ മുറി ( %s ആയിരുന്നു) + ശൂന്യമായ മുറി + + %1$s ഉം വേറെ 1 ആളും + %1$s ഉം വേറെ %2$d പേരും + + + %1$s, %2$s, %3$s കൂടാതെ %4$d പേർ + %1$s, %2$s, %3$s കൂടാതെ %4$d പേരും + + %1$s, %2$s, %3$s പിന്നെ %4$sഉം + %1$s, %2$s പിന്നെ %3$sഉം + %1$s ഉം %2$s ഉം + മുറി ക്ഷണം + %sൽ നിന്നുമുള്ള ക്ഷണം + ഫോൺ നമ്പർ + ഈ - മെയിൽ വിലാസം + ഒരു ശൂന്യമായ മുറിയിൽ വീണ്ടും ചേരാൻ നിലവിൽ സാധ്യമല്ല. + മേട്രിക്സ് പിശക് + നെറ്റ്‌വർക്ക് പിശക് + ചിത്രം അപ്‌ലോഡുചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു + സന്ദേശം അയയ്‌ക്കാനായില്ല + പുനഃക്രമീകരിക്കാൻ കഴിഞ്ഞില്ല + അയച്ചയാളുടെ ഉപകരണം ഈ സന്ദേശത്തിനുള്ള കീകൾ ഞങ്ങൾക്ക് അയച്ചിട്ടില്ല. + ** ഡീക്രിപ്റ്റ് ചെയ്യാൻ കഴിഞ്ഞില്ല: %s ** + %1$s %2$s ന്റെ അധികാര നില മാറ്റി. + നിങ്ങൾ %1$s ന്റെ അധികാര നില മാറ്റി. + ഇച്ഛാനുസൃതം + ഇച്ഛാനുസൃതം (%1$d) + തനത് + മോഡറേറ്റർ + അഡ്മിൻ + നിങ്ങൾ വീഡിയോ കോൺഫറൻസ് പരിഷ്ക്കരിച്ചു + %1$s വീഡിയോ കോൺഫറൻസ് പരിഷ്ക്കരിച്ചു + നിങ്ങൾ വീഡിയോ കോൺഫറൻസ് അവസാനിപ്പിച്ചു + %1$s വീഡിയോ കോൺഫറൻസ് അവസാനിച്ചു + നിങ്ങൾ വീഡിയോ കോൺഫറൻസ് ആരംഭിച്ചു + %1$s വീഡിയോ കോൺഫറൻസ് ആരംഭിച്ചു + നിങ്ങൾ %1$s വിജറ്റ് പരിഷ്ക്കരിച്ചു + %1$s %2$s വിജറ്റ് പരിഷ്ക്കരിച്ചു + നിങ്ങൾ %1$s വിജറ്റ് നീക്കം ചെയ്തു + %1$s %2$s വിജറ്റ് നീക്കം ചെയ്തു + നിങ്ങൾ %1$s വിജറ്റ് ചേർത്തു + %1$s %2$s വിജറ്റ് ചേർത്തു + %1$s നുള്ള ക്ഷണം നിങ്ങൾ സ്വീകരിച്ചു + %1$s %2$sനുള്ള ക്ഷണം സ്വീകരിച്ചു + %1$sനുള്ള ക്ഷണം നിങ്ങൾ റദ്ദാക്കി + %1$s %2$sനുള്ള ക്ഷണം റദ്ദാക്കി + %1$sന് മുറിയിൽ ചേരുന്നതിനുള്ള ക്ഷണം നിങ്ങൾ റദ്ദാക്കി + %2$sന് മുറിയിൽ ചേരുന്നതിനുള്ള ക്ഷണം %1$s റദ്ദാക്കി + നിങ്ങൾ %1$s നെ ക്ഷണിച്ചു + %1$s %2$sനെ ക്ഷണിച്ചു + മുറിയിൽ ചേരാൻ നിങ്ങൾ %1$s ന് ഒരു ക്ഷണം അയച്ചു + %1$s മുറിയിൽ ചേരാൻ %2$s ന് ക്ഷണം അയച്ചു + നിങ്ങളുടെ പ്രൊഫൈൽ %1$s നവീകരിച്ചു + %1$s അവരുടെ പ്രൊഫൈൽ %2$s നവീകരിച്ചു \ No newline at end of file diff --git a/vector/src/main/res/values-nb-rNO/strings.xml b/vector/src/main/res/values-nb-rNO/strings.xml index a4fd61011d..1b4628c913 100644 --- a/vector/src/main/res/values-nb-rNO/strings.xml +++ b/vector/src/main/res/values-nb-rNO/strings.xml @@ -967,7 +967,7 @@ Forkaste endringer Les kvitteringsliste Be om krypteringsnøkler fra andre økter. - URL må starte med http[s]:// + URLen må starte med http[s]:// Du ser på varselet! Klikk på meg! Kunne ikke motta push. Løsningen kan være å installere applikasjonen på nytt. Legg til konto @@ -1027,7 +1027,7 @@ Start chat LAV PRIORITET FAVORITTER - DIREKTIV + KATALOG BLI MED Søking i krypterte rom støttes ikke ennå. FOLK @@ -1154,11 +1154,11 @@ Start ${app_name} på en annen enhet som kan dekryptere meldingen, slik at den kan sende nøklene til denne økten. Forespørsel sendt Nøkkelforespørsel sendt. - SSL-feil: identiteten til jevnaldrende er ikke bekreftet. - Kan ikke nå en hjemmeserver på denne URL-en, sjekk den + SSL Feil: Denne partnerns identitet har ikke blitt verifisert. + Kan ikke nå hjemmetjeneren på denne URLen, vennligst sjekk den Dette er ikke en gyldig adresse for en Matrix tjener - Denne URL-en er ikke tilgjengelig, sjekk den - Kan ikke registrere: e-post eierskap feil + Denne URLen kunne ikke nås, vennligst sjekk den + Klarte ikke registrere: e-posteierskapsfeil Fjern publiseringen Legg til Begynn å chatte @@ -1296,8 +1296,8 @@ \nKlikk på lenken den inneholder for å fortsette opprettelsen av kontoen. Den angitte koden er ikke riktig. Vennligst sjekk. - Det er sendt for mange forespørsler. Du kan prøve på nytt %1$d sekund… - Det er sendt for mange forespørsler. Du kan prøve på nytt %1$d sekunder… + Det er sendt for mange forespørsler. Du kan prøve på nytt om %1$d sekund… + Det er sendt for mange forespørsler. Du kan prøve på nytt om %1$d sekunder… Dette er ikke en gyldig brukeridentifikator. Forventet format: \'@bruker:homeserver.org\' Logg på for å gjenopprette krypteringsnøkler som er lagret eksklusivt på denne enheten. Du trenger dem for å lese alle dine sikre meldinger på hvilken som helst enhet. @@ -1564,15 +1564,15 @@ Filtrer utestengte brukere Endre widgets Aktiver analyse for å hjelpe med å forbedre ${app_name}. - Varsel Personvern - Inkluderer avatar og visningsnavnendringer. + Varselpersonvern + Inkluderer avatar- og visningsnavnendringer. Vis kontohendelser Invitasjoner, spark og utestengelser er ikke påvirket. Vis delta og forlate arrangementer Inkluderer invitasjoner/delta/forlot/spark/utesteng hendelser og avatar/visningsnavnendringer. Vis chateffekter - Vis statens medlemsarrangementer - Vis tidsstempler i 12-timers format + Vis statushendelser angående rommedlemmer + Vis tidsstempler i 12-timersformat Markdown formatering Forhåndsvis lenker i chatten når hjemmeserveren din støtter denne funksjonen. Forhåndsvisning av innebygd URL @@ -1581,14 +1581,14 @@ Hjemmeskjerm Varslingsmål Administrer kryptografinøkler - Bruk en Integration Manager til å administrere roboter, broer, widgets og klistremerkepakker. -\nIntegration Managers mottar konfigurasjonsdata, og kan endre moduler, sende rominvitasjoner og angi strømnivåer på dine vegne. + Bruk en integrasjonshåndterer til å administrere botter, broer, widgets og klistremerkepakker. +\nIntegrasjonshåndterere mottar konfigurasjonsdata, og kan endre moduler, sende rominvitasjoner og angi maktnivåer på dine vegne. Forsinkelse mellom hver synkronisering %s \nSynkroniseringen kan bli utsatt avhengig av ressursene (batteriet) eller enhetens tilstand (hvilemodus). Foretrukket synkroniseringsintervall - Tidsavbrudd for synkronisering forespørsel - Aktiver synkronisering i bakgrunnen + Tidsavbrudd for synkroniseringsforespørsel + Aktiver bakgrunnssynkronisering Du vil ikke bli varslet om innkommende meldinger når appen er i bakgrunnen. ${app_name} vil synkroniseres i bakgrunnen med jevne mellomrom på presis tid (konfigurerbar). \nDette vil påvirke radio og batteribruk, det vises en permanent melding om at ${app_name} lytter etter hendelser. @@ -1599,15 +1599,15 @@ Msgs som inneholder visningsnavnet mitt Konfigurer stille varsler Konfigurer anropsvarsler - Konfigurer lyd varsler - • Varsler vil <b>ikke vise meldingsinnhold<b> + Konfigurer høylytte varsler + • Varsler vil ikke vise meldingsinnhold Ignorer optimalisering Aktiver Start ved oppstart Tjenesten starter når enheten startes på nytt. Tjenesten kunne ikke startes på nytt - Tjenesten stoppet og startet på nytt automatisk. - Varslingstjeneste automatisk omstart - Start Tjeneste + Tjenesten ble stoppet og startet på nytt automatisk. + Automatisk omstart av varslingstjenesten + Start tjeneste Varslingstjenesten kjører ikke. \nPrøv å starte programmet på nytt. Varslingstjenesten kjører. diff --git a/vector/src/main/res/values-nb/strings.xml b/vector/src/main/res/values-nb/strings.xml deleted file mode 100644 index 3a7caefc55..0000000000 --- a/vector/src/main/res/values-nb/strings.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - Du opprettet diskusjonen - Du opprettet rommet - Invitasjonen din - Du sendte et klistremerke. - %1$s sendte et klistremerke. - Du sendte et bilde. - %1$s sendte et bilde. - %1$s: %2$s - %1$s skapte rommet - \ No newline at end of file diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index d346cbff34..b46bacebca 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -2675,4 +2675,70 @@ Pausar Retomar Voltar + Nível de confiança padrão + Selecionado + Vídeo + Algumas mensagens não foram enviadas + Remover foto de perfil + Mudar foto de perfil + Imagem + Importar chave do arquivo + Abrir widgets + Captura de tela + O limite é desconhecido. + O seu servidor local aceita anexos (arquivos, mídia, etc) com tamanhos de até %s. + Versão do servidor + Nome do servidor + Configurações da sala + Sair da chamada atual e mudar para a outra\? + Versão da sala + Mostrar todas as salas na lista de salas, incluindo as salas com conteúdo sensível. + Mostrar salas com conteúdo sensível + Lista de salas + Novo valor + Alterar + Sincronização inicial: +\nBaixando dados… + Sincronização inicial: +\nAguardando resposta do servidor… + Nível de confiança: confiável + Nível de confiança: alerta + Deseja mesmo excluir todas as mensagens não enviadas nesta sala\? + Excluir as mensagens não enviadas + Falha ao enviar as mensagens + Quer cancelar o envio da mensagem\? + Excluir todas as mensagens com falha + Falhou + Enviado + Enviando + Conteúdo do evento + Evento do estado enviado! + Evento enviado! + Evento malformado + Tipo de mensagem ausente + Nenhum conteúdo + Conteúdo do evento + Chave do estado + Tipo + Enviar evento de estado personalizado + Editar conteúdo + Eventos do estado + Enviar evento do estado + Enviar evento personalizado + Explorar o estado da sala + Ferramentas de desenvolvimento + Ver confirmações de leitura + Não notificar + Notificar sem som + Notificar com som + Mensagem não enviada devido a um erro + Verificado + Fechar o selecionador de emojis + Abrir o selecionador de emojis + Esta sala tem rascunho não enviado + + %d entrada + %d entradas + + Limite do envio de arquivo do servidor \ No newline at end of file diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index e401e856ce..1ed1b17734 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -2752,4 +2752,72 @@ Нет учётных данных, неправильная учётная запись пользователя и/или пароль Вернуть + Вы уверены, что хотите удалить все неотправленные сообщения в этой комнате\? + Удалить неотправленные сообщения + Сообщения не удалось отправить + Вы хотите отменить отправку сообщения\? + Удалить все неудачные сообщения + Не удалось + Отправлено + Отправка + Содержание события + Событие статуса отправлено! + Событие отправлено! + Неисправное событие + Отсутствует тип сообщения + Нет содержания + Содержание события + Ключ статуса + Тип + Отправить пользовательское событие статуса + Редактировать содержание + События статуса + Исследовать статус комнаты + Отправить событие статуса + Отправить пользовательское событие + Инструменты для разработчиков + См. подтверждение получения + Не уведомлять + Уведомить без звука + Уведомить со звуком + Сообщение не отправлено из-за ошибки + Проверено + Закрыть выбор эмодзи + Открыть выбор эмодзи + Доверенный уровень доверия + Предупреждающий уровень доверия + Уровень доверия по умолчанию + Выбрано + Видео + В этой комнате есть неотправленный черновик + Некоторые сообщения не были отправлены + Удалить аватар + Сменить аватар + Изображение + Импорт ключа из файла + Открытые виджеты + Скриншот + + %d запись + %d записи + %d записей + %d записей + + Лимит неизвестен. + Ваш домашний сервер принимает вложения (файлы, медиа и т.д.) размером до %s. + Лимит загрузки файла сервера + Версия сервера + Название сервера + Настройки комнаты + Покинуть текущую конференцию и перейти к другой\? + Версия комнаты + Показать все команты в списке комнат, в том числе с чувствительным содержанием. + Показать комнаты с чувствительным содержанием + Список комнат + Новое значение + Сменить + Начальная синхронизация: +\nЗагрузка данных… + Начальная синхронизация: +\nОжидание ответа сервера… \ No newline at end of file diff --git a/vector/src/main/res/values-sr/strings.xml b/vector/src/main/res/values-sr/strings.xml index 2ce03e4a54..065fd3154b 100644 --- a/vector/src/main/res/values-sr/strings.xml +++ b/vector/src/main/res/values-sr/strings.xml @@ -15,7 +15,7 @@ Празна соба (била је %s) Празна соба - %1$s и још %2$d + %1$s и још 1 %1$s и још %2$d %1$s и још %2$d @@ -450,4 +450,312 @@ Ресет Одбаци Паузирај + Неуспешно иницијализовање камере + Веза није успела + Дописник није одговорио. + Поставили сте позив на чекање + %s је ставио позив на чекање + Стави на чекање + Резиме + Врати се на позив + Активни позив (%s) + Видео позив у току… + Позив у току… + Улазни гласовни позив + Улазни видео позив + Улазни позив + Позив… + Позив завршен + Прикључивање позива… + Позив прикључен + Позив + Одабери мелодију за позиве: + Мелодија долазног позива + Дозволи сервер за повратни позив + Користити звоно од ${app_name} за долазне позиве + Затражити потврду пре него што се започнете позив + Спречити случајни позив + Позиви + Тема собе + Име собе + Данас + Јуче + %1$dм %2$dс + %d с + Поништи отпремање\? + Поништи преузимање\? + Мало + Средње + Велико + Оригинално + Погрешно корисничко име/лозинка + SSL грешка. + SSL грешка: Вршњачки идентитет није верификован. + Ово није валидна адреса Matrix сервера + Ова УРЛ адреса није доступна, молимо вас да проверите + Унети важећи URL + Немогућа регистрација: погрешна провера власништва имејла + Немогућност регистрације + Немогућност регистрације: мрежна грешка + Немогућност пријављивања + Није могуће пријавити се: мрежна грешка + URL треба да почне са http[s]:// + Прегледајте и прихватите политику овог сервера: + Ваша лозинка је ресетована. +\n +\nОдјављени сте од свих сесија и више нећете примати пуш обавештења. Да бисте поново омогућили обавештења, поново се пријавите на сваки уређај. + Послат је имејл на %s. Једном када кликнете на везу из поруке, кликните доле. + Регистрација са имејлом и телефонски број одједном није подржана док АПИ не постоји. Биће узети у обзир само телефонски број. +\n +\nМожете да додате свој имејл на свој профил у подешавањима. + Подесите е-пошту за опоравак рачуна. После користите имејл или телефон како би вас открити људи који вас познају. + Подесите е-пошту за опоравак рачуна. После користите имејл или телефон како би вас открити људи који вас познају. + Подесите имејл за опоравак рачуна, а касније како би вас открити људи који вас познају. + Попуните телефонски број како би вас открити људи који вас познају. + Тренутно немате ни један пакет стикера. +\n +\nДодати сада неки\? + Није успело успостављање везе у реалном времену. +\nЗамолите администратора вашег домаћег сервера да конфигурише TURN сервер како би позиви поуздано радили. + Опишите грешку. Шта сте урадили\? Шта сте очекивали да се догоди\? Шта се заправо догодило\? + Укључите историју размене кључева + Састанци користе Jitsi безбедносне и допунске политике. Сви људи тренутно у соби видеће позив да се придруже састанку. + Није успело верификовати имејл адресу: обавезно кликните на линк из примљеног имејла + Мора се унети нова лозинка. + Мора се унети имејл адреса коришћена са вашим налогом. + Да бисте ресетовали лозинку, унесите имејл адресу коришћену са налогом: + Проверио сам своју имејл адресу + Сервер идентитета: + Кућни сервер: + Корисничко име већ употребљено + Овај кућни сервер жели да се увери да нисте робот + Проверити Ваш имејл да би наставили регистрацију + Користите друге опције сервера (напредно) + Заборавили сте лозинку\? + Лозинке нису исте + Неважећи жетон + Недостаје имејл адреса или број телефона + Недостаје број телефона + Недостаје имејл адреса + Овај број телефона је већ коришћен. + Ова имејл адреса је већ коришћена. + Ово не изгледа као важећи број телефона + Ово не изгледа као важећа имејл адреса + Недостаје лозинка + Лозинка премала (6 минимум) + Корисничка имена могу садржати само слова, бројеве, тачке, цртице и подвлаке + Нетачно корисничко име и/или лозинка + Потврдите нову лозинку + Поновити лозинку + Број трелефона (опционо) + Број телефона + Имејл адреса (опционо) + Имејл адреса + Нажалост, ни једна спољна апликација није нађена да би испунила ову акцију. + наставити са… + Послати датотеке + Упали HD + Угаси HD + Позади + Испред + Пребаците камеру + Бежичне слушалице + Слушалице + Звучници + Телефон + Изаберите звучни уређај + ${app_name} Позив није успео + Не питај ме више + Пробајте да користите %s + Позив није успео због погрешне конфигурације сервера + Да ли сте сигурни да желите да започнете видео позив\? + Да ли сте сигурни да желите да започнете гласовни позив\? + Да ли сте сигурни да желите да започнете ћаскање са %s\? + Пошаљи гласовну поруку + Извештај о грешци није успео да се пошаље (%s) + Извештај о грешци је успешно послан + Апликација се последњи пут срушила. Да ли желите да отворите екран за пријаву грешке\? + Изгледа да мрдате телефон из фрустрације. Да ли желите да отворите екран за извештавање о грешкама\? + Ако је могуће, молим вас напишите опис на енглеском језику. + Прикажи све собе у директорију собе, укључујући собе са експлицитним садржајем. + Прикажи собе са експлицитним садржајем + Директоријум собе + Филтрирајте имена заједнице + Филтрирати имена собе + Филтрирати особе + Филтрирати фаворите + Филтрирати имена собе + Нова вредност + Вратити се + Променити + Не можете да поставите позив са собом, причекајте да учесници прихвате позивницу + Не можете да поставите позив са собом + Не може да се започне позив + Конференција је већ у току! + Немате дозволу за започињање позива + Немате дозволу за започињање позива у овој соби + Немате дозволу за започињање конференцијског позива + Немате дозволу за започињање конференцијског позива у овој соби + Потребна вам је дозвола за позивање да започнете конференцију у овој соби + Због недостајућих дозвола, ова акција није могућа. + Због недостајућих дозвола, неке функције могу недостајати… + Не може се покренути позив, покушајте касније + Конференцијски позив у току. +\nПридружи се као %1$s или %2$s + ТрајнаВеза + Почетна синхронизација: +\nПреузимање података… + Почетна синхронизација: +\nЧека се одговор сервера… + Променити дозволе + Променити име собе + Променити видљивост историје + Активирати шифровање собе + Променити главну адресу собе + Променити аватар собе + Променити widget-е + Обавестити све + Уклонити поруке које су други послали + Забранити кориснике + Протерати кориснике + Променити подешавања + Позвати чланове + Слати поруке + Подразумевана улога + Дозволе + Дозволе собе + Сигурно уклонити позив овој корисника\? + Откажи позив + Покажи све поруке овог корисника + Не игнорисањем овог корисника ће поново приказати све његове поруке. + Не игнориши више корисника + Игнорисај + Игнорирање овог корисника уклониће своје поруке из соба које делите. +\n +\nОву акцију можете поништити у било које време у општим подешавањима. + Игнорирај корисника + Снизити права + Нећете моћи да поништите ову промену пошто снизујете ваша права, ако сте последњи привилеговани корисник у соби, неће бити могуће повратити привилегије. + Ауто снизити права\? + Покажи списак сесије + Спомени + Идентификатор, име или имејл + Постави као администратор + Постави као модератор + Рисетуј на нормалан корисник + Изтерај + Уклони забрану + Забрани + Уклони из ове собе + Напусти ову собу + Поништи позив + Позови + СЕСИЈЕ + Приватни разговори + ПОЗИВ + АДМИНИСТРАТОРСКЕ АЛАТКЕ + %1$s пре %2$s + Сада %1$s + Неактиван + Ван везе + На вези + Креирај + Сигурно уклонити %s из овог ћаскања\? + Ова соба није јавна. Нећете моћи поново да се придружите без позива. + Стварно напустити ову собу\? + Напусти собу + + %dд + %dд + %dд + + + %dх + %dх + %dх + + + %dм + %dм + %dм + + + %dс + %dс + %dс + + 1 члан + + %d члан + %d члана + %d чланова + + + %d активан члан + %d активна члана + %d активних чланова + + Додај члана + Ново ћаскање + Додајте сервер идентитета у своја подешавања да бисте извршили ову акцију. + Ово је преглед ове собе. Интеракције собе су онемогућене. + једна соба + Покушавате да приступите %s. Да ли желите да се придружите да бисте учествовали у дискусији\? + %s Вас је позвао да се придружите овој соби + Ићи на прву непрочитану поруку. + Синхронизација… + Отвори заглавље + Листа чланова + Одбаци + Преглед + Придружи се + Избриши + Настави + Послати одговор (НЕшифрован)… + Послати шифрован дговор… + Послати поруку (НЕшифровану)… + Послати шифровану поруку… + %1$s & %2$s & други пишу… + %1$s & %2$s пишу… + %s пише… + Тражи + Е-пошта или Matrix ИД + Само Matrix кориснике + КОРИСНИЧКИ ДИРЕКТОРИЈУМ (%s) + ЛОКАЛНИ КОНТАКТИ (%d) + %1$s %2$s + %1$s и %2$s + "%1$s, " + Разлог + Склањање забране корисника ће му омогућити да се поново придружи соби. + Забрањен корисник ће бити избачен из ове собе и спречити ига да се поново придружи. + Склони забрану корисника + Разлог забране + Забрани корисника + Разлог одбацивања + Одбацити корисник + НЕ + ДА + Сачувати у преузимања\? + Сачувано + Дозволите приступ вашим контактима. + Да бисте скенирали QR кôд, морате дозволити приступ камеру. + Извињавам се. Акција није извршена, због недостајућих дозвола + " +\n +\nМолимо вас да дозволите приступ у следећем прозору да бисте могли да урадите позив." + " +\n +\nМолимо вас да дозволите приступ у следећем прозору да бисте могли да урадите позив." + Информација + Не може да се сними видео + Узми слику или видео + Позив одговорен на друго место + Пошаљи као + Листа група + Листа потврђивања за читање + Послат је захтев + Послат је захтев за кључ. + Нисте још кликнули у везу из послате е-поште + Ово корисничко име је већ коришћено \ No newline at end of file diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml index 87f7144994..31ac8d13c6 100644 --- a/vector/src/main/res/values-sv/strings.xml +++ b/vector/src/main/res/values-sv/strings.xml @@ -2661,4 +2661,15 @@ Skicka anpassad rumshändelse Utforska rumsläge Utvecklingsverktyg + Visa alla rum i rumskatalogen, inklusive rum med stötande innehåll. + Visa rum med stötande innehåll + Är du säker på att du vill radera alla oskickade meddelanden i det här rummet\? + Radera oskickade meddelanden + Meddelanden misslyckades att skickas + Vill du avbryta sändning av meddelanden\? + Radera alla misslyckade meddelanden + Misslyckad + Skickad + Skickar + Rumskatalog \ No newline at end of file diff --git a/vector/src/main/res/values-tr/strings.xml b/vector/src/main/res/values-tr/strings.xml index 13d4a7c313..4739830ea4 100644 --- a/vector/src/main/res/values-tr/strings.xml +++ b/vector/src/main/res/values-tr/strings.xml @@ -1712,4 +1712,108 @@ %s aramayı bekletti Beklet Devam et + Avatarını değiştirdin + %1$s avatarını değiştirdi + %1$s kişisinin davetini geri çektin + %1$s, %2$s kişisinin davetini geri çekti + %1$s kişisini banladınız + %1$s, %2$s kişisini banladı + Daveti reddettin + %1$s daveti reddetti + Odadan ayrıldın + %1$s odadan ayrıldı + Odadan ayrıldın + %1$s odadan ayrıldı + Katıldın + %1$s katıldı + Odaya katıldın + %1$s odaya katıldı + %1$s kişisi seni davet etti + %1$s kişisini davet ettin + %1$s, %2$s kişisini davet etti + Tartışmayı oluşturdun + %1$s tartışmayı oluşturdu + Odayı oluşturdun + %1$s odayı oluşturdu + Senin davetin + %s\'nin daveti + Bir çıkartma gönderdin. + %1$s bir çıkartma gönderdi. + Bir fotoğraf gönderdin. + %1$s bir fotoğraf gönderdi. + %1$s%2$s + %1$s kullanıcısı görünen ismini kaldırdı (önceden şuydu: %2$s) + %1$s görünen adınızı şuna değiştiniz: %2$s + %1$s , %2$s görünen adını şununla değişti: %3$s + Görünen adınızı şuna değiştiniz: %1$s + %1$s görünen adını şuna değişti: %2$s + %1$s kişisini davet ettiniz + %1$s, %2$s kişisini davet etti + Mesaj %1$s tarafından silindi + Mesaj silindi + Oda avatarını kaldırdınız + %1$s oda avatarını kaldırdı + Oda konusunu kaldırdınız + %1$s oda konusunu kaldırdı + Oda ismini kaldırdınız + tüm oda üyeleri. + Aramayı sonlandırdınız. + %s aramayı sonlandırdı. + Aramayı cevapladınız. + %s aramayı cevapladı. + Bir sesli arama başlattınız. + %s bir sesli arama başlattı. + Görüntülü arama başlattınız. + %s bir görüntülü arama başlattı. + Oda ismini şuna değiştirdiniz: %1$s + %1$s oda ismini şuna değiştirdi: %2$s + Oda avatarını değiştirdiniz + %1$s oda avatarını değiştirdi + Konuyu şuna değiştirdiniz: %1$s + %1$s konuyu şuna değiştirdi: %2$s + %1$s adlı kişinin banını kaldırdı + %1$s, %2$s adlı kişinin banını kaldırdı + %1$s adlı kullanıcıyı attı + %1$s, %2$s adlı kullanıcıyı attı + Oda ayarları + Şifreyi göster + Şu zamanda okundu: + Mevcut konferanstan ayrıl ve bir diğerine git\? + Oda sürümü + Mesaj gönderiliyor… + Boş oda + %1$s, %2$s ve %3$s + Özel + %1$s widgetını kaldırdınız + %1$s widgetını eklediniz + Profilinizi güncellediniz %1$s + Oda daveti + Telefon numarası + E-posta adresi + Matrix hatası + Ağ hatası + Görüntü yüklenemedi + Mesaj gönderilemedi + Varsayılan + Video konferansı düzenlediniz + Video konferans %1$s tarafından düzenlendi + Video konferansı sonlandırdınız + Video konferans %1$s tarafından sonlandırıldı + Video konferansı başlattınız + %1$s kişisinin davetini kabul ettiniz + %1$s, %2$s kişisinin davetini kabul etti + %1$s kişisinin davetini geri aldınız + %1$s, %2$s kişisinin davetini geri aldı + %1$s Kişisinin odaya katılma davetini geri aldınız + %1$s, %2$s kişisinin odaya katılma davetini geri aldı + Odaya katılması için %1$s kişisine davet gönderdiniz + %1$s, odaya katılması için %2$s kişisine davet gönderdi + Mesaj %1$s tarafından kaldırıldı [sebep:%2$s] + Mesaj kaldırıldı [sebep: %1$s] + %1$s oda ismini kaldırdı + (avatar da değiştirildi) + Değişiklik yok. + Bu odayı geliştirdiniz. + %s bu odayı geliştirdi. + Uçtan uca şifrelemeyi açtınız (%1$s) \ No newline at end of file diff --git a/vector/src/main/res/values-tzm/strings.xml b/vector/src/main/res/values-tzm/strings.xml index befe8dd878..b8688a3c6b 100644 --- a/vector/src/main/res/values-tzm/strings.xml +++ b/vector/src/main/res/values-tzm/strings.xml @@ -31,4 +31,59 @@ Tisɣal Tisɣal Ssenfel Tisɣal + Rar + Talgoritmet + Tansa + Abda + Dɣer + Dɣer + Azen + Tuzinin + LKM + IFUYLA + Rzu + Ifuyla + Rzu + "%1$s, " + ƔER + + %das + %das + + Agey + Lkem + UHU + YAH + Aɣuri… + Ɣer + Assa + Atilifun + Rzu + Ɣer + Tineɣmisin + Azgal + Rgel + Ṛẓem + Tigawin + Ffeɣ + Agey + Ɣer + neɣ + Avidyu + Ɣer + Kkes + Sfeḍ + Ssiwel + Bḍu + Agem + Ssifeḍ + Azen + Ffel + Ḥḍu + Sser + WAX + Azdam… + Tuzend yat twellaft. + yuzen %1$s yat twellafet. + %1$s: %2$s \ No newline at end of file diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 09aafac703..4d8cd05f6e 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -46,7 +46,7 @@ %1$s приймає запрошення до %2$s ** Неможливо розшифрувати: %s ** Пристрій відправника не надіслав нам ключ для цього повідомлення. - Неможливо відредагувати + Не вдається редагувати Не вдалося надіслати повідомлення Не вдалося завантажити зображення Помилка мережі @@ -94,10 +94,10 @@ Типово Модератор Адміністратор - Ви вилучили %1$s віджет - %1$s вилучає %2$s віджет - Ви додали %1$s віджет - %1$s додає %2$s віджет + Ви вилучили %1$s знадіб + %1$s вилучає %2$s знадіб + Ви додали %1$s знадіб + %1$s додає %2$s знадіб Ви прийняли запрошення до %1$s Ви надіслали запрошення для %1$s приєднатися до кімнати Ви оновили свій профіль %1$s @@ -158,8 +158,8 @@ Порожня кімната (була %s) Власний Власний (%1$d) - Ви змінили віджет %1$s - %1$s змінює віджет %2$s + Ви змінили знадіб %1$s + %1$s змінює знадіб %2$s Ви оновили кімнату. Ви зробили майбутню історію кімнати видимою для %1$s Ви зробили майбутні повідомлення видимими для %1$s @@ -194,7 +194,7 @@ Надіслати Надіслати ще раз Прибрати - Цитата + Цитувати Поділитися Пізніше Переслати @@ -246,7 +246,7 @@ Пошук кімнат Запрошення - Низький пріоритет + Неважливі Бесіди Локальні контакти @@ -285,7 +285,7 @@ Увійти Вийти URL сервера - URL сервера аутентифікації + URL сервера ідентифікації Пошук Почати новий чат Здійснити голосовий виклик @@ -328,7 +328,7 @@ Цей сервер хоче переконатися, що ви не робот Логін вже використовується Сервер: - Сервер аутентифікації: + Сервер ідентифікації: Я перевірив(ла) свою email адресу Для скидання паролю введіть email прив\'язаний до облікового запису: Необхідно ввести email прив\'язаний до вашого облікового запису\'. @@ -426,8 +426,8 @@ 1 учасник Залишити кімнату - Ви впевнені, що хочете залишити кімнату? - Ви впевнені, що хочете вилучити %s з цієї кімнати? + Ви впевнені, що бажаєте залишити кімнату\? + Ви впевнені, що бажаєте вилучити %s з цієї кімнати\? Створити Online Offline @@ -435,7 +435,7 @@ АДМІНІСТРУВАННЯ ВИКЛИК ПРЯМІ ЧАТИ - ПРИСТРОЇ + СЕАНСИ Запросити Залишити цю кімнату Вилучити з цієї кімнати @@ -444,13 +444,13 @@ Зробити звичайним користувачем Зробити модератором Зробити адміністратором - Ігнорувати користувача + Нехтувати Перестати ігнорувати ID користувача, ім\'я або email Згадати Показати Список Пристроїв Ви не зможете скасувати цю дію, оскільки надаєте користувачу той же рівень доступу, що й у вас.\nВи впевнені? - "Впевнені, що хочете запросити %s до цієї кімнати?" + Ви впевнені, що бажаєте запросити %s до цієї кімнати\? Запросити за ID Локальні Контакти (%d) @@ -468,7 +468,7 @@ Надіслати повідомлення (нешифроване)… Зв\'язок із сервером втрачено. Повідомлення не надіслані. %1$s або %2$s зараз? - Повідомлення не надіслані через присутність невідомих пристроїв. %1$s або %2$s зараз? + Повідомлення не надіслані через присутність невідомих сеансів. %1$s або %2$s зараз\? Повторити надсилання скасувати все Надіслати ненадіслані повідомлення знову @@ -515,7 +515,7 @@ КАТАЛОГ ОБРАНІ КІМНАТИ - НИЗЬКИЙ ПРІОРИТЕТ + НЕВАЖЛИВІ ЗАПРОШЕННЯ Почати чат Створити кімнату @@ -575,7 +575,7 @@ Зберігати медіа Налаштування користувача Сповіщення - Ігноровані користувачі + Нехтувані користувачі Інше Розширені Криптографія @@ -586,7 +586,7 @@ Домашній екран Закріплювати кімнати з пропущеними сповіщеннями Закріплювати кімнати з новими повідомленнями - Пристрої + Сеанси Показувати час надсилання для всіх повідомлень Показувати час надсилання у 12-годинному форматі @@ -604,7 +604,7 @@ Надіслати Залоговано як Cервер - Сервер Аутентифікації + Сервер ідентифікації Інтерфейс користувача Мова Оберіть мову @@ -623,8 +623,8 @@ Показувати всі повідомлення %s\? \n \nЗауважте, що це перезавантажить застосунок та може тривати деякий час. - Ви справді бажаєте видалити цю ціль сповіщень? - Справді бажаєте видалити %1$s %2$s? + Ви впевнені, що бажаєте видалити цю ціль сповіщень\? + Ви впевнені, що бажаєте видалити %1$s %2$s\? Оберіть країну Країна Будь ласка, оберіть країну @@ -649,13 +649,13 @@ Позначено як: Улюблені - Низький пріоритет + Неважливі Жодного Доступ та видимість Показувати кімнату при пошуку Доступ - Читабельність історії + Прочитність історії Хто може читати історію повідомлень? Хто має доступ до кімнати? @@ -679,8 +679,8 @@ Наскрізне шифрування Наскрізне шифрування увімкнено Вийдіть з облікового запису, щоб отримати змогу увімкнути шифрування. - Шифрувати лише до перевірених пристроїв - Ніколи не надсилати шифровані повідомлення з цього пристрою неперевіреним пристроям у цій кімнаті. + Шифрувати лише до звірених сеансів + Ніколи не надсилати зашифровані повідомлення з цього сеансу незвіреним сеансам у цій кімнаті. Кімната не має локальної адреси Нова адреса (e.g #foo:matrix.org") @@ -728,10 +728,10 @@ Імпортувати ключі кімнати Імпортувати ключі з локального файлу Імпортувати - Шифрувати лише для перевірених пристроїв - Ніколи не надсилати шифровані повідомлення з цього пристрою неперевіреним пристроям. - НЕ перевірено - Перевірено + Шифрувати лише для звірених сеансів + Ніколи не надсилати зашифровані повідомлення з цього сеансу на незвірені сеанси. + НЕ звірено + Звірено У чорному списку невідомий пристрій порожньо @@ -746,8 +746,12 @@ У майбутньому цей процес верифікації стане більш складним. Я підтверджую, що ключі співпадають - Кімната містить невідомі пристрої - Кімната містить неперевірені невідомі пристрої.\nThis means there is no guarantee that the devices belong to the users they claim to.\nWe recommend you go through the verification process for each device before continuing, but you can resend the message without verifying if you prefer.\n\nUnknown devices: + Кімната містить невідомі сеанси + Кімната містить незвірені невідомі сеанси. +\nЦе означає відсутність будь-яких гарантій у тому, що сеанси належать тим користувачам, які заявляють про належність цих сеансів їм. +\nМи радимо вам звірити кожен сеанс перед тим, як продовжити, проте ви можете перенадіслати повідомлення без звірки, якщо цього бажаєте. +\n +\nНевідомі сеанси: Вибір каталогу кімнат Можливо сервер недоступний чи перевантажений @@ -767,12 +771,12 @@ Найбільший Величезний - Для керування віджетами у цій кімнаті потрібен дозвіл - Помилка створення віджету + Для керування знадобами у цій кімнаті потрібен дозвіл + Помилка створення знадобу Здійснювати конференц дзвінки через Jitsi - Справді бажаєте видалити віджет? + Ви впевнені, що бажаєте видалити знадіб з цієї кімнати\? - Не вдалося створити віджет. + Не вдалося створити знадіб. Не вдалося надіслати запит. Рівень доступу має бути більше 0. Ви не перебуваєте в цій кімнаті. @@ -795,10 +799,10 @@ Використовувати рідну камеру Щойно доданий вами пристрій \'%s\' править ключі шифрування. - Ваш неперевірений пристрій \'%s\' править ключі шифрування. + Ваш незвірений пристрій \'%s\' вимагає ключі шифрування. Почати перевірку Поділитись без перевірки - Знехтувати запитом + Знехтувати запит Помилка виконання команди Команду %s не розпізнано @@ -812,9 +816,9 @@ Спільноти Нема груп Струснути пристрій, щоб повідомити про помилку - Ви дійсно хочете почати новий чат з %s? - Ви впевнені, що бажаєте почати голосовий виклик? - Ви впевнені, що бажаєте почати відео виклик? + Ви впевнені, що бажаєте розпочати новий чат з %s\? + Ви впевнені, що бажаєте розпочати голосовий виклик\? + Ви впевнені, що бажаєте розпочати відео виклик\? Список груп %d зміна членства @@ -832,9 +836,9 @@ Додати ярлик на головний екран Приватність сповіщень Нормальний - Відправити наліпку - Відправити стікер - У вас зараз не має стікерів. + Надіслати наліпку + Надіслати наліпку + У вас поки що не має наліпок. \n \nДодати зараз\? @@ -865,12 +869,12 @@ Завантажити Говорити Очистити - Вдруге запитати ключі шифрування з інших ваших пристроїв. + Вдруге запитати ключі шифрування з інших ваших сеансів. Запит ключа відправлений. Запит відправлений Вібрація при згадуванні користувача - Деактивація аккаунта - Деактивувати мій аккаунт + Деактивація облікового запису + Деактивувати мій обліковий запис Конфіденційність сповіщень Надати дозвіл Вибрати другий варіант @@ -972,10 +976,10 @@ %1$s у %2$s - %d активний віджет - %d активні віджети - %d активних віджетів - + %d активний знадіб + %d активні знадоби + %d активних знадобів + %d активних знадобів Пропущено обов’язковий параметр. Недійсний параметр. @@ -1047,14 +1051,14 @@ Продовження розмови тут Ця кімната є продовженням іншої розмови Натисніть сюди, щоб побачити старіші повідомлення - Перевищено ресурсний ліміт + Перевищено ресурсне обмеження Зв’язатися з адміністратором зв’яжіться з Вашим адміністратором Цей сервер перевищив один із ресурсних лімітів, тому деякі користувачі не зможуть увійти в систему. - Цей сервер перевищив один із ресурсних лімітів. - Цей сервер досяг свого місячного ліміту активних користувачів, тому деякі з них не зможуть увійти в систему. - Цей сервер досяг свого місячного ліміту активних користувачів. - Будь ласка, %s для збільшення цього ліміту. + Цей домашній сервер досяг одного зі своїх ресурсних обмежень. + Цей сервер досяг свого місячного обмеження на активних користувачів, тому деякі з них не зможуть увійти в систему. + Цей сервер досяг свого місячного обмеження на активних користувачів. + Будь ласка, %s для збільшення цього обмеження. Будь ласка, %s для продовження використання цього сервісу. Поступове завантаження співрозмовників Підвищити продуктивність, завантажуючи співрозмовників лише при першому перегляді. @@ -1122,8 +1126,8 @@ Увімкнути HD Вимкнути HD Основна - Фронтальна - Переключити камеру + Передня + Перемкнути камеру Бездротова гарнітура Гарнітура Динамік @@ -1139,15 +1143,15 @@ Ви впевнені, що бажаєте вийти\? Покласти слухавку Відхилити - Прийняти + Відповісти Відмовити Огляд Знехтувати Перервати Готово Пропустити - Не вдалося видалити віджет - Не вдалося додати віджет + Не вдалося видалити знадіб + Не вдалося додати знадіб Ви не можете здійснити дзвінок із самим собою Почати аудіо-зустріч Почати відеозустріч @@ -1168,7 +1172,7 @@ Резервне копіювання ключів… Якщо вийти зараз, ви втратите свої зашифровані повідомлення Перевірка сеансу - Стікер + Наліпка Галерея Файл Додати зображення з @@ -1216,10 +1220,10 @@ Призначити роль Надіслати Номери телефонів - Email адреси + Електронні адреси Скасувати запрошення 🔐️ Приєднуйтесь до мене в ${app_name} - Привіт, поспілкуйся зі мною в ${app_name}: %s + Привіт! Спілкуймося в ${app_name}: %s Запросити друзів Всі спільноти Показувати заглушку на місці видалених повідомлень @@ -1243,7 +1247,7 @@ Надіслати електронні адреси та номери телефонів Надіслати електронні адреси та номери телефонів Керування електронними адресами та номерами телефонів, пов’язаними з вашим обліковим записом Matrix - Електорнні адреси та номери телефонів + Електронні адреси та номери телефонів Надіслати історію запитів спільного доступу до ключів Стрічка подій Непрочитані повідомлення @@ -1290,9 +1294,9 @@ Налаштування сповіщень викликів Налаштування гучних сповіщень Початкова синхронізація… - Видимі електронні адреси + Виявні електронні адреси Недійсна відповідь виявлення домашнього сервера - Налаштуйте свою видимість. + Налаштуйте свою виявність. Видимість Не вдалося встановити зв’язок у режимі реального часу. \nПопросіть адміністратора вашого домашнього сервера налаштувати сервер TURN для надійної роботи викликів. @@ -1316,7 +1320,7 @@ Дізнатись більше Безпека Безпека та приватність - Ми раді повідомити, що змінили назву! Ваш застосунок оновлено й ви ввійшли у свій обліковий запис. + Ми раді повідомити вас, що ми змінили назву! Ваш застосунок оновлено й ви увійшли у свій обліковий запис. Зміну параля ще не завершено. \n \nЗупинити змінювання пароля\? @@ -1325,7 +1329,7 @@ Резервне копіювання розпочато Неочікувана помилка Ключ відновлення - Створення ключа відновлення за допомогою парольної фрази, цей процес може тривати кілька секунд. + Створення відновлювального ключа за допомогою парольної фрази, цей процес може тривати кілька секунд. Поділитися ключем відновлення з… Будь ласка, створіть копію Стоп @@ -1341,9 +1345,9 @@ Зберегти ключ відновлення Я створив копію Готово - Зберігайте ключ відновлення десь дуже надійно, наприклад, у менеджері паролів (або сейфі) - Ваш ключ відновлення — це мережа безпеки — ви можете використовувати його для відновлення доступу до ваших зашифрованих повідомлень, якщо ви забудете свою парольну фразу. -\nТримайте ключ відновлення десь дуже надійно, наприклад, у менеджері паролів (або сейфі) + Тримайте відновлювальний ключ у якомусь дуже надійному місці, наприклад, у менеджері паролів (або сейфі) + Ваш відновлювальний ключ — це мережа безпеки — ви можете використовувати його для відновлення доступу до ваших зашифрованих повідомлень, якщо ви забудете свою парольну фразу. +\nТримайте відновлювальний ключ у якомусь дуже надійному місці, наприклад, у менеджері паролів (або сейфі) Створюється резервна копія ключів. Успішно ! (Додатково) Налаштування за допомогою ключа відновлення @@ -1371,7 +1375,7 @@ Запит на розподіл ключів Поділитися Перевірити - Неперевірений сеанс запитує ключі шифрування. + Незвірений сеанс запитує ключі шифрування. \nНазва сеансу: %1$s \nОстанні відвідини: %2$s \nЯкщо ви не ввійшли в інший сеанс, знехтуйте цим запитом. @@ -1380,7 +1384,7 @@ \nОстанні відвідини: %2$s \nЯкщо ви не ввійшли в інший сеанс, знехтуйте цим запитом. Для продовження потрібно прийняти Умови користування цією службою. - Немає активних віджетів + Немає активних знадобів Керувати інтеграціями Менеджер інтеграції не налаштовано. Читати захищені DRM засоби масової інформації @@ -1388,25 +1392,25 @@ Використовувати камеру Заблокувати все Дозволити - Цей віджет хоче використовувати такі ресурси: + Цей знадіб хоче використовувати такі ресурси: На жаль, конференц-дзвінки з Jitsi не підтримуються на старих пристроях (пристрої з ОС Android нижче 6.0) Ідентифікатор кімнати - Ідентифікатор віджета + Ідентифікатор знадобу Ваша тема Ваш ідентифікатор користувача URL-адреса зображення профілю Ваше видиме ім\'я Скасувати доступ для мене Відкрити в браузері - Перезавантажити віджет - Не вдалося завантажити віджет. + Перезавантажити знадіб + Не вдалося завантажити знадіб. \n%s Використання може спричинити обмін даними з %s: За його використання може бути встановлено файли cookie та відбуватися обмін даними з %s: - Цей віджет додав: - Завантажити віджет - Віджет - Активні віджети + Цей знадіб додав: + Завантажити знадіб + Знадіб + Активні знадоби ПОДАННЯ %1$s: %2$s %3$s %1$s: %2$s @@ -1512,8 +1516,8 @@ Включає події запрошення/приєднання/виходу/видалення/заборони та зміни зображень профілю/видимих імен. Показати стан подій учасників кімнати Керування криптографічними ключами - Використовуйте Менеджер інтеграції для керування ботами, мостами, віджетами та пакетами наклейок. -\nМенеджери інтеграції отримують дані конфігурації та можуть змінювати віджети, надсилати запрошення до кімнати та надавати права від вашого імені. + Використовуйте Менеджер інтеграції для керування ботами, мостами, знадобами та пакунками наліпок. +\nМенеджери інтеграції отримують дані конфігурації та можуть змінювати знадоби, надсилати запрошення до кімнати та надавати права від вашого імені. Інтеграції %d секунда @@ -1607,14 +1611,14 @@ \nЩоб запобігти їх повторному приєднанню, замість цього слід заблокувати їх. Причина викидання Викинути користувача - Дійсно скасувати запрошення для цього користувача\? + Ви впевнені, що бажаєте скасувати запрошення для цього користувача\? Скасувати запрошення Зняття ігнорування з цього користувача знову покаже всі повідомлення від нього. - Не ігнорувати користувача - Ігнорування цього користувача призведе до видалення його повідомлень з кімнат, якими ви ділитесь. + Рознехтувати користувача + Нехтування цього користувача призведе до видалення його повідомлень з усіх кімнат, де ви обидва є учасниками. \n \nВи можете будь-коли змінити цю дію в загальних налаштуваннях. - Ігнорувати користувача + Нехтувати користувача Понизити Ви не зможете скасувати цю зміну, оскільки понижуєте свої права, якщо ви останній привілейований користувач у кімнаті, неможливо буде повернути собі привілеї. Понизитися\? @@ -1637,8 +1641,8 @@ Лише згадки Всі повідомлення Всі повідомлення (гучно) - Ігнорувати користувача - Перестати ігнорувати + Нехтувати користувача + Рознехтувати Підпис Алгоритм Версія @@ -1673,7 +1677,7 @@ Налаштування захисту Захистіть доступ PIN-кодом та біометричними даними. Захист доступу - Цей сеанс довірений для безпечного обміну повідомленнями, оскільки ви його підтвердили: + Цей сеанс довірений для безпечного обміну повідомленнями, оскільки ви його звірили: %d активний сеанс %d активні сеанси @@ -1687,7 +1691,7 @@ Потрібна повторна автентифікація Для виконання цієї дії ${app_name} вимагає ввести свої облікові дані. Якщо скасувати, ви не зможете читати зашифровані повідомлення на новому пристрої, а інші користувачі не довірятимуть йому - Якщо скасувати, ви не зможете читати зашифровані повідомлення на цьому пристрої, а інші користувачі довірятимуть йому + Якщо скасувати, ви не зможете читати зашифровані повідомлення на цьому пристрої, а інші користувачі не довірятимуть йому Керування сеансами Просимо зачекати… Перевірка входу @@ -1697,9 +1701,361 @@ Повідомлення тут не захищено наскрізним шифруванням. Увімкнути наскрізне шифрування… Повідомлення в цій кімнаті наскрізно зашифровані. - Перевірити цей сеанс + Звірити цей сеанс Перевірте цей сеанс, підтвердивши, що на екрані партнера з’являються такі цифри Перевірте цей сеанс, підтвердивши, що на екрані партнера з’являються ці емоджі - Перевірте цей сеанс, щоб позначити його надійним. Довірені сеанси партнерів дають вам додаткову впевненість під час використання наскрізно зашифрованих повідомлень. - Перевірте цей сеанс, щоб позначити його надійним та надати йому доступ до зашифрованих повідомлень. Якщо ви не входили в цей сеанс, ваш обліковий запис може бути зламано: + Звірте цей сеанс, щоб позначити його надійним. Довірені сеанси партнерів дають вам додаткову впевненість під час використання наскрізно зашифрованих повідомлень. + Звірте цей сеанс, щоб позначити його надійним та надати йому доступ до зашифрованих повідомлень. Якщо ви не входили в цей сеанс, ваш обліковий запис може бути зламано: + Назва або ID (#example:matrix.org) + Назва + Фільтрувати за іменем користувача або ID… + Це початок цієї розмови. + Назва кімнати + Показувати такі подробиці, як назви кімнат та вміст повідомлень. + Тема кімнати (необов\'язково) + Тема + Ви впевнені, що хочете вилучити (видалити) цю подію\? Зауважте, що, якщо ви видалите назву кімнати або зміните тему, це може скасувати зміну. + %s щоб люди знали, про що ця кімната. + Додати тему + "Тема: " + Назва кімнати + Тема + Відкрити умови %s + Завантаження інших доступних мов… + Інші доступні мови + Поточна мова + Поділіться цим кодом з людьми, щоб вони змогли сканувати його, щоб додати вас і почати спілкуватися в чаті. + Мій код + Поділитися моїм кодом + Сканувати QR-код + Ми не можемо запросити користувачів. Перевірте користувачів, яких ви хочете запросити та повторіть спробу. + + Запрошення надіслано для %1$s та ще одній особі + Запрошення надіслано для %1$s та ще %2$d особам + Запрошення надіслано для %1$s та ще %2$d осіб + Запрошення надіслано для %1$s та ще %2$d осіб + + + Одна особа + %1$d особи + %1$d осіб + %1$d осіб + + Більше + ДОКЛАДНІШЕ + Повідомлення в цій кімнаті захищені наскрізним шифруванням. Дізнайтеся більше та підтвердьте користувачів у їхньому профілі. + Змінювати тему + Оновлювати кімнату + Надсилати події m.room.server_acl + Змінювати дозволи + Змінювати назву кімнати + Змінювати видимість історії + Вмикати шифрування кімнати + Змінювати основну адресу кімнати + Змінювати аватар кімнати + Змінювати знадоби + Сповіщати всіх + Вилучати повідомлення, надіслані іншими + Блокувати користувачів + Викидати користувачів + Змінювати налаштування + Запрошувати користувачів + Надсилати повідомлення + Типова роль + У вас немає дозволу на оновлення ролей, необхідних для зміни різних частин кімнати + Виберіть ролі, необхідні для зміни різних частин кімнати + Дозволи + Перегляд та оновлення ролей, необхідних для зміни різних частин кімнати. + Залишити кімнату + Залишити кімнату + Залишити + Залишити поточну конференцію та перейти до іншої\? + Це не загальнодоступна кімната. Ви не зможете знову приєднатися без запрошення. + У вас немає дозволу на ввімкнення шифрування в цій кімнаті. + Дозволи кімнати + Ознайомтеся з непрочитаними повідомленнями тут + Ви впевнені, що бажаєте видалити усі не надіслані повідомлення з цієї кімнати\? + Посилання %1$s спрямовує вас на інший сайт: %2$s. +\n +\nВи впевнені, що бажаєте продовжити\? + Ви вийшли + Вилучити… + Наліпка + Використовувати ботів, мости, знадоби та пакунки наліпок + Зв\'язок із сервером втрачено + Не знайдено жодної правки + Історія правок + Рівень довіри + Не довірений + Довірений + Шукайте зелений щит аби переконатись, що користувач є довіреним. Довіртесь усім користувачам у кімнаті аби переконатись, що кімната є безпечною та захищеною. + Для максимальної безпеки використовуйте інші довірені засоби зв\'язку або робіть це під час особистої зустрічі. + Не довірений вхід + Звірення цього сеансу позначить його довіреним для вас і для партнера. + Задля максимальної безпеки ми радимо зробити це під час особистої зустрічі або з використанням інших довірених засобів зв\'язку. + Підтвердьте вашу тотожність, звіривши цей вхід з одного з ваших інших сеансів та надавши йому доступ до зашифрованих повідомлень. + Звірте усі свої сеанси, щоб переконатись у безпечності вашого облікового запису та повідомлень + Сеанси + Не вдалось отримати сеанси + Ви не маєте доступу до цього повідомлення, бо відправник не довіряє вашому сеансу + Позначити довіреним + Зашифроване незвіреним пристроєм + Цей сеанс є довіреним для безпечного обміну повідомленням тому що його було звірено %1$s (%2$s): + Звірено + Ваш новий сеанс тепер звірений. В нього є доступ до ваших зашифрованих повідомлень, а інші користувачі бачитимуть його, як довірений. + Звірено %s + Захищені повідомлення з цим користувачем є наскрізно зашифрованими та є непрочитними для сторонніх осіб. + Ви успішно звірили цей сеанс. + Звірено! + Резервна копія має недійсний підпис з незвіреного сеансу %s + Резервна копія має недійсний підпис зі звіреного сеансу %s + Резервна копія має дійсний підпис з незвіреного сеансу %s + Резервна копія має дійсний підпис зі звіреного сеансу %s. + СТВОРИТИ + На вміст надіслано скаргу + Надіслано скаргу, як на спам + Надіслано скаргу, як на неприйнятне + Забагато помилок, вам довелось вийти + Незашифроване + надсилає сніг ❄️ + надсилає конфетті 🎉 + Надсилає вказане повідомлення зі снігом + Надсилає вказане повідомлення з конфетті + + Показати пристрій, з якого ви можете звірити цей сеанс просто зараз + Показати %d пристрої, з яких ви можете звірити цей сеанс просто зараз + Показати %d пристроїв, з яких ви можете звірити цей сеанс просто зараз + Показати %d пристроїв, з яких ви можете звірити цей сеанс просто зараз + + Ви розпочнете знову, але без історії повідомлень, без довірених пристроїв та користувачів + Вдавайтесь до цього лише за умов відсутності жодного пристрою, з якого ви можете звірити поточний пристрій. + Якщо ви скинете все + Скинути все + Резервна копія не може бути дешифрована цією парольною фразою: переконайтесь, що відновлювальна парольна фраза зазначена правильно. + Не знаєте вашої відновлювальної парольної фрази\? Ви можете %s. + Відновлювальна парольна фраза + Забули або втратили усі можливості для відновлення\? Скинути все + Використати файл + Скористатись відновлювальними парольною фразою або ключем + Скористатись відновлювальними парольною фразою або ключем + Використовуйте найостаннішій ${app_name} на ваших інших пристроях, ${app_name} Web, ${app_name} для комп\'ютерів, ${app_name} iOS, ${app_name} для Android, або будь-який інший, здатний до перехресного підписування, Matrix-клієнт + Використовуйте найостаннішій ${app_name} на ваших інших пристроях: + Якщо ви не можете доступитись до чинного сеансу + Використайте чинний сеанс, щоб звірити цей сеанс, таким чином надавши йому доступ до зашифрованих повідомлень. + Підтвердьте вашу тотожність, звіривши цей вхід та надавши йому доступ до зашифрованих повідомлень. + Звірте новий вхід, що доступається до вашого облікового запису: %1$s + Звірте цей вхід + Очікування… + Торкніться, щоб переглянути та звірити + Новий вхід. Це були ви\? + Оновити + Скинути ключі + або будь-який інший, здатний до перехресного підписування, Matrix-клієнт + Перехресне підписування не ввімкнене + Перехресне підписування увімкнено. +\nКлючі не є довіреними + Перехресне підписування увімкнено +\nКлючі є довіреними. +\nЗакриті ключі невідомі + Перехресне підписування увімкнено +\nЗакриті ключі на пристрої. + Перехресне підписування + %s приєднується. + Шифрування увімкнено + Ви прийняли запрошення для %1$s. Причина: %2$s + %1$s приймає запрошення для %2$s. Причина: %3$s + Ви відкликали запрошення для %1$s приєднатись до кімнати. Причина: %2$s + %1$s відкликає запрошення для %2$s приєднатись до кімнати. Причина: %3$s + %1$s надсилає %2$s запрошення приєднатись до кімнати. Причина: %3$s + Ви надіслали %1$s запрошення приєднатись до кімнати. Причина: %2$s + Ви заблокували %1$s. Причина: %2$s + %1$s заблоковано %2$s. Причина: %3$s + Ви розблокували %1$s. Причина: %2$s + %1$s розблоковує %2$s. Причина: %3$s + Ви викинули %1$s. Причина: %2$s + %1$s викидає %2$s. Причина: %3$s + Ви берете участь в цьому виклику зараз + Це початок вашої історії листування з %s. + Повідомлення тут є наскрізно зашифрованими. +\n +\nВаші повідомлення захищені замками, тож лише ви та отримувачі мають унікальні ключі для їхнього відмикання. + Повідомлення тут є наскрізно зашифрованими. +\n +\nВаші повідомлення захищені замками, тож лише ви та отримувачі мають унікальні ключі для їхнього відмикання. + Це початок %s. + %1$s відхиляє цей виклик + Ви відхилили цей виклик %1$s + %1$s розпочинає виклик + Ви розпочали виклик + Швидкі реакції + Користувачі + Під час переадресації трапилась помилка + Переадресувати + З\'єднати + Перетелефонувати + Відновити + Клавіатура + Ви утримали виклик + %s утримали виклик + Утримати + + Призупинений виклик + %1$d призупинені виклики + %1$d призупинених викликів + %1$d призупинених викликів + + Поточний виклик (%1$s) + + 1 поточний виклик (%1$s) · 1 призупинений виклик + 1 поточний виклик (%1$s) · %2$d призупинені виклики + 1 поточний виклик (%1$s) · %2$d призупинених викликів + 1 поточний виклик (%1$s) · %2$d призупинених викликів + + Змінити мережу + Змінити + Додати за matrix ID + Push-сповіщення вимкнено + Не вдалось розблокувати користувача + Блокування від %1$s + Відкликати запрошення для %1$s\? + Відкликати запрошення + Введіть ваш %s, щоб продовжити. + Підтвердити %s + Згенерувати ключ повідомлення + Зазначити %s + Пароль облікового запису + Ключ повідомлення + Подобається + Гаразд + Реакції + Ви приєднались. + Запрошування користувачів… + Переглянути умови + Умови використання + Переглянути історію редагувань + Входження до кімнати… + Запрошення отримано від %s + Запрошення надіслано до %1$s та %2$s + Запрошення надіслано до %1$s + Згоду користувача не було надано. + Використовувати %1$s + Початкова синхронізація: +\nЗвантаження даних… + Початкова синхронізація: +\nОчікування відповіді сервера… + Запросити користувачів + ЗАПРОСИТИ + Розпочніть друкувати, щоб отримати результати + Нещодавні + Відомі користувачі + Пропозиції + Контакти + Пошук контактів у Matrix + Контакти + Отримання контактів… + QR-код не зіскановано! + QR-код не дійсний (недійсний URI)! + Цей QR-код не дійсний + Чекаємо на %s… + Майже все! Чекаємо на підтвердження… + Майже все! Чи показує інший пристрій такий самий щит\? + Ні + Так + Майже все! Чи показує %s такий самий щит\? + QR-код + Зображення QR-коду + QR-код + Пошук за іменем або ID + Додати за QR-кодом + Очікування + Зазначте адресу сервера ідентифікації + В іншому разі, ви можете зазначити адресу будь-якого іншого сервера ідентифікації + Ваш домашній сервер (%1$s) пропонує використовувати %2$s як ваш сервер ідентифікації + Спочатку погодьтесь з умовами користування сервером ідентифікації в налаштуваннях. + Спочатку налаштуйте сервер ідентифікації. + Цей сервер ідентифікації є застарілим. ${app_name} підтримує лише API V2. + Від\'єднатись від сервера ідентифікації %s\? + Погодьтесь з умовами використання сервера ідентифікації (%s), щоб дозволити вашу виявність за електронною адресою та номером телефону. + Ви не нехтуєте жодних користувачів + Скаргу на неприйнятний вміст було надіслано. +\n +\nЯкщо ви більше не бажаєте бачити жодного вмісту від цього користувача, ви можете знехтувати його, щоб приховати його повідомлення. + Скаргу на спам було надіслано. +\n +\nЯкщо ви більше не бажаєте бачити жодного вмісту від цього користувача, ви можете знехтувати його, щоб приховати його повідомлення. + Скаргу на вміст було надіслано. +\n +\nЯкщо ви більше не бажаєте бачити жодного вмісту від цього користувача, ви можете знехтувати його, щоб приховати його повідомлення. + ЗНЕХТУВАТИ КОРИСТУВАЧА + Ви зараз оприлюднюєте електронні адреси та номери телефонів для сервера ідентифікації %1$s. Вам необхідно буде перепід\'єднатись до %2$s, щоб припинити оприлюднення. + Увімкнути детальні звіти. + Розблокувати історію + Надіслані лютострусом детальні звіти допоможуть розробникам отримати більше інформації. Навіть якщо цю функцію увімкнено, застосунок не зберігає ані вмісту повідомлень, ані будь-якої іншої особистої інформації. + Користувач видалив подію через %1$s + Подію видалено користувачем + Інша причина… + Неприйнятний вміст + Спам + Поскаржитись на цей вміст + ПОСКАРЖИТИСЬ + Причина скарги + Зреагували: %s + Зреагувати + Завершити + Відповісти + Редагувати + Зрозуміло + Riot тепер називається Element! + Файл %1$s було звантажено! + Звантаження файлу %1$s… + Надсилання файлу (%1$s / %2$s) + Шифрування файлу… + Струс виявлено! + Потрясіть вашим пристроєм, щоб перевірити поріг чутливості + Поріг чутливості + Лютоcтрус + Режим розробника вмикає приховані функції та може зробити застосунок менш стабільним. Лише для розробників! + Відкликати згоду + Надати згоду + Обраний вами сервер ідентифікації не має жодних умов використання. Продовжуйте лише якщо довіряєте власнику сервісу + Сервер ідентифікації не має умов використання + Зазначте, будь ласка, адресу сервера ідентифікації + Неможливо під\'єднатись до сервера ідентифікації + Зазначте адресу сервера ідентифікації + Чи погоджуєтесь ви надіслати дані ваших контактів (номери телефонів та/або електронні адреси) на налаштований сервер ідентифікації(%1$s) задля виявлення відомих вам наявних контактів\? +\n +\nДля поліпшення приватності дані буде захешовано перед надсиланням. + Ласкаво просимо! + Повторити + Від\'єднання від вашого сервера ідентифікації означатиме, що ви не будете виявними для інших користувачів та не зможете запрошувати інших через електронну пошту або номер телефону. + Ви наразі не використовуєте жодного сервера ідентифікації. Для того, щоб виявляти інших та бути виявним для знайомих вам наявних контактів, налаштуйте такий сервер нижче. + Ви зараз використовуєте %1$s для того, щоб виявляти інших та бути виявним для знайомих вам наявних контактів. + Змінити сервер ідентифікації + Налаштувати сервер ідентифікації + Від\'єднати сервер ідентифікації + Сервер ідентифікації + Бути виявним для інших + Жодного сервера ідентифікації не налаштовано. Вам необхідно скинути ваш пароль. + Ви не використовуєте жодного сервера ідентифікації + Криптографічна інформація недоступна + Обмеження невідомі. + Ваш домашній сервер дозволяє надсилати файли розміром до %s. + Обмеження сервера на розмір файлів + Версія сервера + Назва сервера + Встановити новий пароль облікового запису… + Попередній перегляд відкритих кімнат поки що не підтримується в ${app_name} + (відредаговано) + Востаннє відредаговано %1$s %2$s + Цей виклик було завершено + Новий вхід + Увійти + Увійти + Увійти знову + Увійти з Matrix ID + Увійти з Matrix ID + Увійти знову + Увійти + Увійти до %1$s + Увійти через %s \ No newline at end of file diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index e40e95da82..82ec8fc36d 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -25,8 +25,8 @@ 所有聊天室成员。 任何人。 未知(%s)。 - %1$s 开启了端到端加密(%2$s) - %1$s 请求了一次 VoIP 会议 + %1$s 开启了端对端加密(%2$s) + %1$s 请求了 VoIP 会议 VoIP 会议已开始 VoIP 会议已结束 (头像也被更改) @@ -43,7 +43,7 @@ 手机号码 %1$s 撤回了对 %2$s 的邀请 %1$s 让未来的聊天室历史记录对 %2$s 可见 - %1$s 更新了他的个人档案 %2$s + %1$s 更新了他的资料 %2$s %1$s 向 %2$s 发送了加入聊天室的邀请 %1$s 接受了 %2$s 的邀请 无法撤回 @@ -104,8 +104,8 @@ %1$s 为此聊天室移除了主地址。 %1$s 已允许访客加入聊天室。 %1$s 已禁止访客加入聊天室。 - %1$s 已开启端到端加密。 - %1$s 已开启端到端加密(无法识别的演算法 %2$s)。 + %1$s 已开启端对端加密。 + %1$s 已开启端对端加密(无法识别的演算法 %2$s)。 %1$s 创建了这个聊天室 您发送了一张图片。 您发送了一张贴纸。 @@ -134,23 +134,23 @@ 您接听了通话。 您结束了通话。 您已让未来的聊天室记录对 %1$s 可见 - 您开启了端到端加密(%1$s) + 您开启了端对端加密(%1$s) 您升级了此聊天室。 您请求了 VoIP 会议 您移除了聊天室名称 您移除了聊天室主题 %1$s 移除了聊天室头像 您移除了聊天室头像 - 您更新了您的个人档案 %1$s + 您更新了您的资料 %1$s 您向 %1$s 发送了加入聊天室的邀请 您已撤回了对 %1$s 加入聊天室的邀请 您接受了 %1$s 的邀请 - %1$s 添加了 %2$s 小部件 - 您添加了 %1$s 小部件 - %1$s 移除了 %2$s 小部件 - 您移除了 %1$s 小部件 - %1$s 修改了 %2$s 小部件 - 您修改了 %1$s 小部件 + %1$s 添加了 %2$s 挂件 + 您添加了 %1$s 挂件 + %1$s 移除了 %2$s 挂件 + 您移除了 %1$s 挂件 + %1$s 修改了 %2$s 挂件 + 您修改了 %1$s 挂件 管理员 审核员 默认 @@ -182,8 +182,8 @@ 您移除了此聊天室的主地址。 您已允许访客加入聊天室。 您已禁止访客加入聊天室。 - 您已开启端到端加密。 - 您已开启端到端加密(无法识别的算法 %1$s)。 + 您已开启端对端加密。 + 您已开启端对端加密(无法识别的算法 %1$s)。 您已离开。理由:%1$s %1$s 已离开。理由:%2$s 您已加入。理由:%1$s @@ -268,7 +268,7 @@ 进度(%s%%) 主服务器 URL 身份服务器 URL - 登入 + 登录 注册 提交 跳过 @@ -298,7 +298,7 @@ 主服务器: 身份服务器: 我已验证了我的电子邮箱地址 - 要重置您的密码,请输入与您的帐号关联的电子邮箱地址: + 要重置您的密码,请输入与您的账号关联的电子邮箱地址: 必须输入与您账号关联的电子邮箱地址。 必须输入新密码。 URL 必须以 http[s]:// 开头 @@ -395,11 +395,11 @@ %d 秒 通话已连接 通话正在连接… - 为了发送或保存附件,${app_name} 需要访问您的图片和视频库。 + 为发送或保存附件,${app_name} 需要权限以访问您的图片和视频库。 \n -\n请在接下来弹出的窗口中授权允许访问,以便应用能够从您的手机发送文件。 - 为了拍照或进行视频通话,${app_name} 需要访问您的相机。 - 为了进行语音通话,${app_name} 需要访问您的麦克风。 +\n请在接下来的弹出窗口中授权允许访问,以便从此设备中发送文件。 + ${app_name} 需要权限来访问您的相机,以拍摄照片或进行视频通话。 + ${app_name} 需要权限以访问您的麦克风来进行语音通话。 您试图访问聊天室 %s。您是否愿意加入这个聊天室? 管理工具 私聊 @@ -421,8 +421,8 @@ 本地联系人(%d 个) %1$s 和 %2$s 正在输入… %1$s 和 %2$s 及其他人正在输入… - 发送一条加密消息… - 发送一条消息(未加密)… + 发送加密消息… + 发送消息(未加密)… 与服务器的连接已断开。 消息未发送。现在 %1$s 还是 %2$s? 因为未知设备的存在,消息未发送。现在 %1$s 还是 %2$s? @@ -455,7 +455,7 @@ 隐私政策 手机号码 应用信息 - 启用这个账户的通知 + 启用这个账号的通知 启用这个设备的通知 来自私聊的消息 来自群聊的消息 @@ -497,7 +497,7 @@ 只有成员(从他们被邀请开始) 只有成员(从他们加入开始) 只有被邀请的人 - 除游客之外任何知道这个聊天室链接的人 + 任何知道这个聊天室链接的人,游客除外 任何知道这个聊天室链接的人,包括游客 被封禁的用户 高级 @@ -505,30 +505,30 @@ 地址 这些是实验性功能,可能会出现不可预料的错误。请谨慎使用。 端对端加密 - 您需要注销来启用加密。 - 在此聊天室中、使用本设备时,从不向未验证的设备发送加密消息。 + 您需要注销以启用加密。 + 对于当前会话,从在不此聊天室中向未验证的设备发送加密消息。 这个聊天室没有本地地址 - 新地址(如 #foo:matrix.org) + 新地址(例如 #foo:matrix.org) 别名格式无效 “%s” 不是有效的别名格式 这个聊天室已启用加密。 这个聊天室已禁用加密。 - 启用加密 -\n(警告:无法再禁用!) + 启用加密 +\n(警告:启用后无法禁用!) 目录 端对端加密信息 事件信息 用户 ID - 导出端到端聊天室密钥 + 导出端对端聊天室密钥 导出聊天室密钥 导出密钥到本地文件 导出 - 输入密码 - 确认密码 - 端到端聊天室密钥已经被保存到“%s”。 + 输入密语 + 确认密语 + 端对端聊天室密钥已经被保存到“%s”。 \n \n注意:如果应用被卸载,此文件可能将会被移除。 - 导入端到端聊天室密钥 + 导入端对端聊天室密钥 导入聊天室密钥 从本地文件导入密钥 仅向已验证的设备发送加密消息 @@ -557,9 +557,9 @@ 问题反馈发送失败(%s) 阅读 无效令牌 - api存在之前,不支持同时通过电子邮件和电话号码进行注册。我们只考虑电话号码。 + 在新的 API 出现之前,尚不支持同时使用电子邮件和电话号码注册,所以只有电话号码会被记录。 \n -\n您可以在设置中添加您的电子邮件到您的个人资料。 +\n在设置中,您可以在个人资料里添加您的电子邮件。 用户名已被使用 无法注册:电子邮箱所有权验证失败 无法识别指定的访问令牌 @@ -582,20 +582,20 @@ " \n \n请在接下来弹出的窗口中授权允许访问。" - ${app_name}需要许可才能访问您的摄像机和麦克风来执行视频通话。 + ${app_name} 需要权限以访问您的摄像机和麦克风来进行视频通话。 \n -\n请在接下来弹出的窗口中授权允许访问。 +\n请在接下来的弹出窗口中授权允许访问,以便进行通话。 对不起。因为权限不足,操作已取消 保存至下载? 移除 - 此邀请已发送至未与此账户关联的 %s。 -\n您可能希望用一个不同的账户登录,或者把这个电子邮箱加入到你的账户。 + 此邀请已发送至未与此账号关联的 %s。 +\n您可能希望用一个不同的账号登录,或者把这个电子邮箱加入到你的账号。 这是此聊天室的预览。与聊天室的交互已禁用。 通话 您将不能撤销这个修改,因为您正在让这个用户和您拥有相同的特权级别。 \n您确定吗? - 这可能意味着有人在恶意劫持您的通讯,或者您的手机不信任远程服务器的数字证书。 - 如果服务器管理员说这是正常的,请确保以下的指纹与管理员提供的指纹相符。 + 这可能意味着有人正在恶意劫持您的流量,或者您的手机不信任远程服务器提供的数字证书。 + 如果服务器管理员说这是预期的情况,请确保下面的指纹与管理员提供的指纹相匹配。 报告这个内容的原因 目录 邀请 @@ -644,17 +644,15 @@ 发送至 已读标签清单 发送为 - ${app_name} 需要访问您的通讯录,才能根据电子邮箱地址和手机号码查找其他 Matrix 用户。 - -请在接下来的弹出窗口中授权允许访问。 - ${app_name}可以检查您的通讯录,以根据电子邮件和电话号码找到其他Matrix用户。 + ${app_name} 可以检查您的通讯录,并基于他们的邮箱地址和电话号码,来查找其他 Matrix 用户。若您同意本应用以此目的访问您的通讯录,请在接下来的弹出窗口中授权允许访问。 + ${app_name} 可以检查您的通讯录,并基于他们的邮箱地址和电话号码,来查找其他 Matrix 用户。 \n -\n你同意为此目的分享你的通讯录吗\? +\n您是否同意本应用以此目的访问您的通讯录\? 空闲 仅 Matrix 用户 - 您的手机信任的证书已被更改。这非常反常。建议您不要接受此新证书。 - 证书已从以前受信任的更改为不受信任的证书。服务器可能已更新其证书。请联系管理员并核对服务器的指纹。 - 请仅在服务器管理员已经发布了与上述指纹相匹配的指纹的情况下接受该证书。 + 证书已从一个先前受您的设备信任的证书更改为另一个。这非常反常!建议您不要接受此新证书。 + 证书已从曾受信任的证书更改为不受信任的证书。服务器可能已更新其证书,请联系管理员并核对服务器的指纹。 + 请仅在服务器管理员发布了与上述指纹匹配的指纹的情况下接受该证书。 成员 ID 的格式不正确。应该是一个电子邮箱地址或 Matrix ID,如 “@localpart:domain” 联系人 @@ -671,13 +669,13 @@ 正在等待验证 代码 访问和可见性 - 聊天室访问 + 聊天室访问权限 实验室 端对端加密已激活 %s 已尝试在这个聊天室的时间线上加载一个特定的时间点,但无法找到它。 公开名称 为验证此设备是否可信,请通过其他方式(例如面对面交换或拨打电话)与其拥有者联系,并询问他们该设备的用户设置中的密钥是否与以下密钥匹配: - 如果它们不匹配,您通信的安全性可能会受到影响。 + 如果它们不匹配,您通讯的安全性可能会受到影响。 这个聊天室包含未经验证的未知设备。 \n这意味着无法保证该设备属于其声称的用户。 \n我们建议您在继续操作之前,先验证每个设备,但如果您愿意也可以不验证而重新发送消息。 @@ -760,12 +758,12 @@ 黑色主题 通知声音 使用12小时制显示时间戳 - 您需要权限来管理这个聊天室的小部件 - 创建小部件失败 + 您需要权限来管理这个聊天室的挂件 + 创建挂件失败 用 jitsi 创建会议通话 - 您确定要删除这个小部件吗? + 您确定要删除这个挂件吗? - 无法创建小部件。 + 无法创建挂件。 发送请求失败。 特权级别必须是正整数。 您不在这个聊天室。 @@ -841,8 +839,8 @@ %d 位成员 - 无效的社区 ID - “%s” 不是一个有效的社区 ID + 社区 ID 无效 + “%s” 不是有效的社区 ID %d 条未读消息 @@ -859,7 +857,7 @@ %d 个聊天室 - 已启用 %d 个小部件 + 已启用 %d 个挂件 社区名称 @@ -893,29 +891,33 @@ • 通知通过 Firebase Cloud Messaging 发送 • 通知只含有元数据 • 通知不会显示消息内容 - 新的社区ID(如 +foo:matrix.org) + 新的社区 ID(如 +foo:matrix.org) 社区管理员没有提供这个社区的具体描述。 标准 低隐私模式 - 停用账户 - 停用我的账户 + 停用账号 + 停用我的账号 发送统计分析数据 ${app_name} 会收集匿名统计数据来帮助我们改进程序。 请允许资料分析以帮助我们改进 ${app_name}。 是的,我愿意帮助! - 停用账户 - 这将使您的账户永远不再可用。您将不能登录,或使用相同的用户 ID 重新注册。您的账户将退出所有已加入的聊天室,身份服务器上的账户信息也会被删除。此操作是不可逆的。 停用您的账户不会默认忘记您发送的消息。如果您希望我们忘记您发送的消息,请勾选下面的选择框。 Matrix 中的消息可见性类似于电子邮件。我们忘记您的消息意味着您发送的消息不会被发给新注册或未注册的用户,但是已收到您的消息的注册用户依旧可以看到他们的副本。 - 请在我停用账户的同时忘记我发送的所有消息(警告:这将导致未来的用户看到残缺的对话) + 停用账号 + 这将使您的账号永远不再可用。您将无法登录,也不能使用相同的用户 ID 重新注册。您的账号将退出所有已加入的聊天室,您在身份服务器上的账号信息也会被删除。此操作是不可逆的。 +\n +\n停用您的账号不会默认忘记您已发送的消息。如果您希望我们忘记您发送的消息,请勾选下面的选择框。 +\n +\nMatrix 中的消息可见性类似于电子邮件。我们忘记您的消息意味着您发送的消息不会被发给新注册或未注册的用户,但是已收到您的消息的注册用户依旧可以看到这些消息的副本。 + 请在我停用账号的同时忘记我发送的所有消息(警告:这将导致未来的用户看到残缺的对话) 请输入您的密码以继续: - 停用账户 + 停用账号 发送贴纸 发送贴纸 您目前没有启用任何贴纸包。 \n \n要添加一些吗? - • 通知中的消息内容从 Matrix 主服务器直接安全的获取 + • 通知中的消息内容直接从 Matrix 主服务器安全地获取 • 通知含有消息与元数据 - 这个聊天室不会显示任何社区的徽章 + 此聊天室不会显示任何社区徽章 缺少所需的参数。 无效参数。 样例 ID @@ -936,20 +938,20 @@ 请输入您的密码。 发言 如果可能的话,请使用英文撰写问题描述。 - 发送加密的回复… + 发送加密回复… 发送回复(未加密)… 发送前预览媒体文件 使用回车键发送消息 显示动作 - 依照 ID 封禁用户 - 依照 ID 解禁用户 + 按照 ID 封禁用户 + 按照 ID 解禁用户 设置用户的权限等级 - 依照 ID 取消用户管理员权限 - 依照 ID 邀请用户进入当前聊天室 - 依照别名加入聊天室 + 按照 ID 取消用户管理员权限 + 按照 ID 邀请用户进入当前聊天室 + 按照别名加入聊天室 离开聊天室 设置聊天室主题 - 依照 ID 踢出用户 + 按照 ID 踢出用户 更改您显示的昵称 打开/关闭 markdown 修复 Matrix Apps 管理 @@ -1021,10 +1023,10 @@ 通知已在系统设置中禁用。 \n请检查系统设置。 打开设置 - 帐号设置。 - 您的帐号已启用通知。 - 您的账户已禁用通知。 -\n请检查账户设置。 + 账号设置。 + 您的账号已启用通知。 + 您的账号已禁用通知。 +\n请检查账号设置。 启用 设备设置。 已为此设备启用通知。 @@ -1056,7 +1058,7 @@ 启用开机时启动 检查后台限制 电池优化 - 若主服务器支持本功能,在聊天时预览链接内容。 + 若主服务器支持此功能,在聊天中预览链接内容。 发送正在输入通知 让聊天室中的其他用户知道您正在输入。 Markdown 格式化 @@ -1065,10 +1067,10 @@ 点击已阅回执以显示所有已经阅读过某条消息的用户。 显示加入与离开事件 邀请、移除与封禁事件不受影响。 - 显示账户变动事件 + 显示账号变动事件 包括头像与显示名称的变动。 后台连接 - ${app_name}需要保持低影响的后台连接,以便获得可靠的通知。 + ${app_name} 需要保持低影响的后台连接,以便获得可靠的通知。 \n下一个屏幕中,系统将提示您允许 ${app_name} 始终在后台运行,请点击“允许“。 授予权限 在验证您的电子邮件地址时发生了一个错误。 @@ -1089,8 +1091,8 @@ 您的主服务器尚未支持延迟加载聊天室成员,请稍候再试。 通过仅载入最近聊天中出现的聊天室成员来提升性能。 延迟加载聊天室成员 - 已停用 Markdown。 - 已启用 Markdown。 + Markdown 已禁用。 + Markdown 已启用。 视频通话中… 自动重启通知服务 服务被停止,并已自动重启。 @@ -1106,23 +1108,23 @@ ${app_name} 未被电池优化影响。 如果设备在未充电的情况下关屏静置一段时间,其将进入打盹模式(Doze)。这将阻止应用访问网络并延后其运行、同步、与响铃。 忽略电池优化 - 请输入用于加密密钥备份的密码。您在恢复此备份时需要使用此密码。 - 创建密码 - 密码必须对应 + 请输入用于加密被导出密钥的密语。恢复此备份时,必须输入相同的密语才能导入密钥。 + 创建密语 + 密语必须对应 指令 %s 需要更多参数,或者有些参数不正确。 没有可用的 Google Play Services APK。消息通知可能不能正常工作。 密钥备份 使用备份密钥 密钥备份尚未完成,请等待… - 如果您此时登出账号,您将会失去您的已加密信息 - 密钥备份进行中。如果您此时登出账号将无法再访问您的已加密信息。 - 您的所有设备都应当启用安全密钥备份以确保您不会失去您的已加密信息的访问权。 - 我不想要我的已加密信息 - 密钥备份中… + 如果您此时登出账号,您将会失去您的已加密消息 + 密钥备份进行中。如果您此时登出账号将无法再访问您的已加密消息。 + 您的所有设备都应当启用安全密钥备份以确保您不会失去您的已加密消息的访问权。 + 我不想要我的已加密消息 + 正在备份密钥… 使用备份密钥 确定吗? 备份 - 如果您在登出账号之前不备份密钥,您将失去您的已加密信息的访问权。 + 如果您在登出账号之前不备份密钥,您将失去您的已加密消息的访问权。 留下 跳过 完成 @@ -1148,34 +1150,34 @@ 选择指示灯颜色,震动,铃声… 加密密钥管理 省流量模式使用了特定的过滤器,所以状态更新和输入状态通知将会被过滤掉。 - 已加密信息恢复 + 恢复已加密消息 管理密钥备份 静音 请输入一个用户名。 - 请输入密码 - 密码太弱 - 如果您想要 ${app_name} 生成一个恢复密钥,请删除密码。 + 请输入密语 + 密语太弱了 + 如果您想要 ${app_name} 生成一个恢复密钥,请删除密语。 没有可用的 Matrix 会话 - 已加密信息永不丢失 + 永不丢失已加密消息 加密聊天室中的信息会被端对端加密以确保安全。只有您和拥有密钥的接收方可以读取这些信息。 \n \n安全地备份您的密钥以免丢失信息。 开始使用备份密钥 (高级) 手动导出密钥 - 使用密码以保护您的备份。 - 我们将会在主服务器上保存一份您的密钥的加密拷贝。设置一个密码来保护您的备份的安全。 + 使用密语保护您的备份。 + 我们将会在主服务器上为您的密钥保存一份加密拷贝。设置一个密语来保护您的备份的安全。 \n -\n为了最大的安全性,这个密码应当与您的账号密码不同。 - 设置密码 - 备份创建中 +\n为了最大的安全性,此密语应当与您的账号密码不同。 + 设置密语 + 正在创建备份 或者用一个恢复密钥来保护您的备份,将其保存到另一个安全的地方。 (高级)设置一个恢复密钥 成功! 正在备份您的密钥。 - 您的恢复密钥是一张安全网 - 如果您忘记了密码您可以利用它重获您的已加密信息的访问权。 -\n请将您的恢复密钥保存在一个非常安全的地方,比如密码管理器中 (或保险箱里) - 将您的恢复密钥保存在一个非常安全的地方,比如密码管理器中 (或保险箱里) + 您的恢复密钥是一张安全网——如果您忘记了密语,您可以利用它重获您的已加密消息的访问权。 +\n请将您的恢复密钥保存在一个非常安全的地方,比如密码管理器中(或保险箱里) + 将您的恢复密钥保存在一个非常安全的地方,比如密码管理器中(或保险箱里) 完成 我已经制作了一份拷贝 保存恢复密钥 @@ -1185,8 +1187,8 @@ \n \n警告:如果应用被卸载,此文件可能会被删除。 请制作一份拷贝 - 分享恢复密钥 … - 用密码来生成恢复密钥,此过程可能会花费几秒钟。 + 分享恢复密钥… + 正在使用密语来生成恢复密钥,此过程可能会花费几秒钟。 恢复密钥 意外错误 备份开始 @@ -1194,19 +1196,19 @@ 您确定吗? 如果您登出账号或者丢失此设备,您可能再也无法访问您的信息。 正在获取备份的版本 … - 使用恢复密码解锁您的已加密历史消息 + 使用恢复密语解锁您的已加密历史消息 使用您的恢复密钥 - 不知道您的恢复密码,您可以 %s 。 + 如果不知道您的恢复密语,您可以 %s。 使用恢复密钥解锁您的已加密历史消息 输入恢复密钥 - 信息恢复 + 消息恢复 丢失了恢复密钥?您可以在设置中新建一个。 - 无法使用此密码解密备份:请检查您输入的恢复密码是否正确。 + 无法使用此密语解密备份:请检查您输入的恢复密语是否正确。 网络错误:请检查您的网络连接并重试。 - 备份恢复中: - 恢复密钥计算中 … - 密钥下载中 … - 密钥导入中 … + 正在恢复备份: + 正在计算恢复密钥… + 正在下载密钥… + 正在导入密钥… 解锁历史 请输入恢复密钥 无法使用此恢复密钥解密备份:请检查您输入的恢复密钥是否正确。 @@ -1231,8 +1233,8 @@ 备份具有已验证设备 %s 的无效签名 备份具有未验证设备 %s 的无效签名 无法获得备份 (%s)的信任信息。 - 要在此设备上使用密钥备份,请立即使用密码或恢复密钥进行恢复。 - 备份删除中 … + 要在此会话中使用密钥备份,请立即使用密语或恢复密钥进行恢复。 + 正在删除备份… 备份(%s)删除失败 删除备份 要从此服务器中删除您备份的加密密钥吗?您将无法再使用恢复密钥来读取加密的历史消息。 @@ -1241,22 +1243,22 @@ \n \n如果您并未设置新的恢复方法,可能是有攻击者试图侵入您的账号。请立即更改您的账号密码并在设置中设定一个新的恢复方法。 那是我 - 永不丢失已加密信息 + 永不丢失已加密消息 开始使用备份密钥 - 永不丢失已加密信息 + 永不丢失已加密消息 使用备份密钥 新加密信息密钥 管理密钥备份 - 密钥备份中 … + 正在备份密钥… 所有密钥都已备份 - %d 个密钥备份中 … + 正在备份 %d 个密钥… 版本 算法 签名 忽略 - 以单点登录方式登入 + 以单点登录方式登录 无法连接到此 URL,请检查 您的设备使用了过时的 TLS 安全协议,容易受到攻击,为保证安全,您将无法进行连接 按回车发送消息 @@ -1374,17 +1376,17 @@ 拒绝 没有设置身份服务器。 服务器的错误配置导致通话失败 - 请要求您的家庭服务器 (%1$s) 的管理员配置 TURN 服务器,以使通话可靠地工作。 + 请要求您的主服务器 (%1$s) 的管理员配置 TURN 服务器,以使通话可靠地工作。 \n \n或者,您可以尝试使用 %2$s 的公共服务器,但这将不那么可靠,并且它将与该服务器共享您的 IP 地址。您也可以在“设置”中进行管理。 尝试使用 %s 不要再问我 - 设置用于帐户恢复的电子邮件,然后就可以让认识您的人选择性探索到您。 + 设置用于恢复账号的电子邮件,然后就可以让认识您的人选择性探索到您。 设定电话,然后就可以让认识您的人选择性探索到您。 - 设定电子邮件以供帐号复原。 然后就可以让认识您的人用电子邮件或电话选择性探索到您。 - 设定电子邮件以供帐号复原。 然后就可以让认识您的人用电子邮件或电话选择性探索到您。 + 设定电子邮件以供恢复账号。然后就可以让认识您的人用电子邮件或电话选择性探索到您。 + 设定电子邮件以供恢复账号。然后就可以让认识您的人用电子邮件或电话选择性探索到您。 這不是有效的 Matrix 服务器位置 - 无法在此 URL 找到家庭服务器,请检查 + 无法在此 URL 找到主服务器,请检查 允许后备呼叫协助服务器 播放 暂停 @@ -1394,7 +1396,7 @@ 通知 ${app_name} 呼叫失败 无法建立实时连接。 -\n请要求您的家庭服务器管理员配置 TURN 服务器以使通话可靠工作。 +\n请要求您的主服务器管理员配置 TURN 服务器以使通话可靠工作。 选择声音设备 电话 扬声器 @@ -1407,7 +1409,7 @@ 打开 HD SSL 错误:尚未验证对等端身份。 SSL 错误。 - 当您的家庭服务器未提供时将使用 %s 作为辅助(在通话时将分享您的 IP 地址) + 当您的主服务器未提供时将使用 %s 作为辅助(在通话时将分享您的 IP 地址) 活动通话 (%s) 返回通话 在您的设置中添加身份服务器以执行此操作。 @@ -1456,7 +1458,7 @@ 设置安全备份 重置安全备份 在此设备上设置 - 通过在您的服务器上备份加密密钥保障加密消息和数据的访问权。 + 通过在您的服务器上备份加密密钥,防止失去对加密信息和数据的访问。 为您已有的备份生成新的安全密钥或设置新的安全口令。 这将替换您的当前密钥或短语。 发现 @@ -1468,30 +1470,30 @@ %d 个封禁用户 - 公开名称(对通信参与者可见) - 会话的公开名称对通信的参与者可见 + 公开名称(对与您通讯的人可见) + 会话的公开名称对与您通讯的人可见 成功导出密钥 %1$s: %2$s %1$s: %2$s %3$s 查看 - 活动小部件 - 小部件 - 载入小部件 - 此小部件添加者: + 活动挂件 + 挂件 + 载入挂件 + 此挂件添加者: 使用它会设置 cookie 并与 %s 分享数据: 使用它会与 %s 分享数据: - 无法载入小部件。 + 无法载入挂件。 \n%s - 重载小部件 + 重载挂件 在浏览器中打开 撤消我的访问权限 您的昵称 您的头像 URL 您的用户 ID 您的主题 - 小部件 ID + 挂件 ID 聊天室 ID - 小部件想使用以下资源: + 挂件想使用以下资源: 允许 阻止全部 使用相机 @@ -1500,18 +1502,18 @@ 未配置集成管理器。 若要继续请接受服务条款。 恢复密钥已保存。 - 您的家庭服务器上已存在备份 + 您的主服务器上已存在备份 您似乎已在另一个会话中设置密钥备份。您想要将其替换为正在创建的吗? 安全备份 保护加密信息及数据的访问权 设置安全备份 由于无效或过期的凭据您已登出。 - 验证会话已将其标记为可信。当使用端到端加密消息时信任参与者的会话将给您额外的内心平静。 - 验证会话将标记其为可信,同时将您的会话对对方标记为可信。 + 验证此会话以将其标记为可信任。当使用端对端加密消息时,信任参与者的会话可以使您更加安心。 + 验证会话将标记其为可信任,同时将您的会话对对方标记为可信任。 通过确认以下表情符号出现在对方的屏幕上来验证此会话 通过确认屏幕上对方显示以下数字来验证此会话 您收到传入验证请求。 - 与此用户的安全消息端到端加密,无法被第三方读取。 + 与此用户的安全消息端对端加密,无法被第三方读取。 对方取消了验证。 \n%s 验证已取消。 @@ -1528,7 +1530,7 @@ 用户不匹配 您未使用身份服务器 未配置身份服务器,需要重置您的密码。 - 您似乎正在试图连接到另一个家庭服务器。您想要登出吗? + 您似乎正在试图连接到另一个主服务器。您想要登出吗? 加入一个聊天室开始使用应用。 您已经跟上了! 您没有未读消息 @@ -1551,7 +1553,7 @@ 将此聊天室发布到聊天室目录 获取信任信息时发生错误 获取密钥备份数据时发生错误 - 从文件 \"%1$s\" 导入端到端密钥。 + 从文件 \"%1$s\" 导入端对端密钥。 其他第三方通知 您已经在查看此聊天室! 注册令牌 @@ -1577,7 +1579,7 @@ 发送新私聊消息 查看聊天室目录 名称或 ID (#example:matrix.org) - 在时间线中启用滑动回复 + 启用在时间线中滑动回复 在主屏幕上添加未读通知选项卡。 链接已复制到剪贴板 通过 matrix ID 添加 @@ -1590,7 +1592,7 @@ 服务条款 审核条款 可被其他人发现 - 使用机器人,小部件和贴纸包 + 使用机器人,挂件和贴纸包 已读于 身份服务器 断开身份服务器 @@ -1617,7 +1619,7 @@ 同意身份服务器 (%s) 服务条款使您可以通过电子邮件地址或电话号码被发现。 启用详细日志。 当您发送 RageShake 时详细日志将帮助开发者提供更多日志。即使启用,应用也不会记录消息内容或任何其他私有数据。 - 接收您的家庭服务器条款和条件后请重试。 + 接收您的主服务器条款和条件后请重试。 服务器似乎响应时间太长,这可能是由于连接不良或服务器错误引起的。 请稍后再试。 发送附件 打开导航菜单 @@ -1645,10 +1647,10 @@ 贴纸 无法处理共享数据 媒体 - 此聊天室中无媒体 + 此聊天室中暂无媒体 文件 %1$s 于 %2$s - 此聊天室中无文件 + 此聊天室中暂无文件 垃圾信息 不合适的内容 自定义报告…… @@ -1668,9 +1670,9 @@ 此内容已报告为不合适。 \n \n如果您不希望再看到此用户的更多内容,您可以忽略他们以隐藏他们的消息。 - ${app_name} 需要权限在磁盘上保存您的端到端密钥。 + ${app_name} 需要权限以在磁盘上保存您的端对端密钥。 \n -\n请在下个弹窗中允许访问以便手动导出密钥。 +\n请在接下来的弹出窗口中授权允许访问,以便您手动导出密钥。 目前没有网络连接 忽略用户 全部消息(嘈杂) @@ -1696,7 +1698,7 @@ 扩展 & 自定义您的体验 开始吧 选择服务器 - 就像电子邮件,账户有一个家,尽管您可以与任何人聊天 + 就像电子邮件,账号有一个家,尽管您可以与任何人聊天 在最大的公共服务器上免费加入数百万用户 面向组织的高级托管 了解更多 @@ -1706,9 +1708,9 @@ 连接到 %1$s 连接到 Element Matrix 服务 连接到自定义服务器 - 登入 %1$s + 登录 %1$s 注册 - 登入 + 登录 使用单点登录继续 Element Matrix 服务地址 地址 @@ -1716,23 +1718,23 @@ 输入 Modular Element 或您想使用的服务器地址 输入您想使用的服务器的地址 载入页面时出错:%1$s (%2$d) - 应用无法登录到此家庭服务器。家庭服务器支持以下登录类型:%1$s。 + 应用无法登录到此主服务器。主服务器支持以下登录类型:%1$s。 \n \n您想要通过网页客户端登录吗? - 抱歉,此服务器不接受新账户。 - 应用无法在此服务器上创建账户。 -\n + 抱歉,此服务器不接受新账号。 + 应用无法在此服务器上创建账号。 +\n \n您想要通过网页客户端注册吗? - 电子邮件未关联到任何账户。 + 电子邮件未关联到任何账号。 在 %1$s 上重置密码 验证邮件将发送到您的收件箱以确认设置您的新密码。 下一个 电子邮件 新密码 注意! - 更改您的密码将重置所有会话上的端到端加密密钥,从而使加密聊天记录无法读取。在重设密码之前,请设置“密钥备份”或从另一个会话中导出聊天室密钥。 + 更改您的密码将重置所有会话上的端对端加密密钥,从而使加密聊天记录无法读取。在重设密码之前,请设置“密钥备份”或从另一个会话中导出聊天室密钥。 继续 - 电子邮件未链接到任何账户 + 电子邮件未链接到任何账号 检查您的收件箱 验证电子邮件已发送到 %1$s。 点击链接以确认您的新密码。跟随包含的链接验证后,请点击下方。 @@ -1746,7 +1748,7 @@ \n \n是否中止密码更改过程? 设置电子邮件地址 - 设置电子邮件用于恢复您的帐户。之后,您可以选择允许您认识的人通过电子邮件发现您。 + 设置电子邮件用于恢复您的账号。之后,您可以选择允许您认识的人通过电子邮件发现您。 电子邮件 电子邮件(可选) 下一个 @@ -1770,31 +1772,31 @@ 下一个 用户名已占用 注意 - 您的帐户尚未创建。 + 您的账号尚未创建。 \n \n是否中止注册过程? 选择 matrix.org 选择 Element Matrix Services - 选择自定义家庭服务器 + 选择自定义主服务器 请进行人机验证 接受条款以继续 请检查您的电子邮件 我们向 %1$s 发送了电子邮件。 -\n请点击其中包含的链接继续账户创建。 +\n请点击其中包含的链接继续账号创建。 输入的验证码不正确。请检查。 - 过时的家庭服务器 - 此家庭服务器运行的版本过旧以至于无法连接。要求您的家庭服务器管理员升级。 + 过时的主服务器 + 此主服务器运行的版本过旧以至于无法连接。要求您的主服务器管理员升级。 发送了太多请求。您可以在 %1$d 秒后重试… - 或者,如果您已经拥有账户并知道您的 Matrix 标识符和密码,您可以使用这种方式: + 或者,如果您已经拥有账号并知道您的 Matrix 标识符和密码,您可以使用这种方式: 使用 Matrix ID 登录 使用 Matrix ID 登录 - 如果您在家庭服务器上设置了账户,在下方使用您的 Matrix ID(例 @user:domain.com)和密码。 + 如果您在主服务器上设置了账号,在下方使用您的 Matrix ID(例 @user:domain.com)和密码。 Matrix ID 如果您不知道您的密码,返回并重置。 这不是一个有效的用户标识符。期望的格式:\'@user:homeserver.org\' - 无法找到有效的家庭服务器。请检查您的标识符 + 无法找到有效的主服务器。请检查您的标识符 您已登出 这可能由于多种原因: \n @@ -1803,25 +1805,25 @@ \n• 您已从其他会话删除了此会话。 \n \n• 您的服务器管理员出于安全原因已取消您的访问权限。 - 重新登入 + 重新登录 您已登出 - 登入 - 您的家庭服务器 (%1$s) 管理员将您从您的账户 %2$s (%3$s) 登出。 + 登录 + 您的主服务器 (%1$s) 管理员将您从您的账号 %2$s (%3$s) 登出。 登录以恢复仅存储在此设备上的加密密钥。 您需要使用它们在任何设备上阅读所有安全消息。 - 登入 + 登录 密码 清除个人数据 注意:您的个人数据(包括加密密钥)仍存储在此设备上。 \n -\n如果您不再使用此设备,或想登入另一个帐户,请清除它。 +\n如果您不再使用此设备,或想登录另一个账号,请清除它。 清除全部数据 清除数据 是否清除当前存储在此设备上的全部数据? -\n再次登入以访问您的帐户数据和消息。 - 除非您登入以恢复加密密钥,否则您将无法访问安全消息。 +\n再次登录以访问您的账号数据和消息。 + 除非您登录以恢复加密密钥,否则您将无法访问安全消息。 清除数据 当前会话用于用户 %1$s 而您提供了用户 %2$s 的凭证。${app_name} 不支持此功能。 -\n请先清除数据,然后重新登入另一个账户。 +\n请先清除数据,然后重新登录另一个账号。 您的 matrix.to 链接更是不正确 描述太短 初始同步… @@ -1841,19 +1843,19 @@ 发生意外错误时,${app_name} 可能更经常崩溃 在明文消息前添加 ¯\\_(ツ)_/¯ 启用加密 - 一旦启用,加密无法禁用。 + 加密一经启用,便无法禁用。 您的电子邮件域无权注册此服务器 - 不可信登入 + 未信任的登录 匹配 不匹配 确认下方独特表情以相同顺序出现在他们的屏幕上,以验证此用户。 - 为了获得最高的安全性,请使用其他可信通信方式或亲自确认。 - 寻找绿色盾牌以确保用户可信。信任聊天室中的所有用户以确保聊天室的安全。 + 为了获得最高的安全性,请使用其他可信任的通讯方式,或者当面确认。 + 寻找绿色盾牌以确保用户可信任。信任聊天室中的所有用户以确保聊天室的安全。 不安全 以下其中一项可能会受到威胁: \n -\n - 您的家庭服务器 -\n - 您验证的用户连接到的家庭服务器 +\n - 您的主服务器 +\n - 您验证的用户连接到的主服务器 \n - 您或其它用户的网络连接 \n - 您或其他用户的设备 视频。 @@ -1885,10 +1887,10 @@ 为了提高安全性,请通过检查两个设备上的一次性代码来验证 %s。 \n \n为了获得最大的安全性,请亲自进行。 - 此聊天室的消息不是端到端加密。 - 该聊天室的消息端到端加密。 + 此聊天室的消息未经端对端加密。 + 该聊天室的消息已被端对端加密。 \n -\n您的消息使用锁进行保护,只有您和接收者才能使用唯一的密钥解锁。 +\n您的消息受加密保护,并且只有您和消息接收者拥有唯一解密密钥。 安全 了解更多 更多 @@ -1902,16 +1904,16 @@ 离开聊天室 正在离开聊天室… 管理员 - 审查员 + 协管员 自定义 邀请 用户 %1$s 管理员 - %1$s 审查员 + %1$s 的协管员 %1$s 默认权限 %2$s 自定义权限 (%1$d) - ${app_name} 无法处理 \'%1$s\' 类型事件 - ${app_name} 无法处理 \'%1$s\' 类型消息 + ${app_name} 无法处理类型为 \'%1$s\' 的事件 + ${app_name} 无法处理类型为 \'%1$s\' 的消息 ${app_name} 在渲染 id 为 \'%1$s\' 的事件内容时遇到了一个问题 取消忽略 该会话无法与您的其他会话共享此验证。 @@ -1922,34 +1924,34 @@ 发送彩虹色给定表情 时间线 消息编辑器 - 启用端到端加密… - 一旦启用,加密无法禁用。 + 启用端对端加密… + 加密一经启用,便无法禁用。 是否启用加密? - 启用后,将无法禁用聊天室加密。服务器无法看到加密聊天室中发送的消息,只有聊天室的参与者才能看到。启用加密可能会阻止许多机器人和桥接正常工作。 + 聊天室加密一经启用,便无法禁用。在加密聊天室中,发送的消息无法被服务器看到,只能被聊天室的参与者看到。启用加密可能会使许多机器人和桥接无法正常运作。 启用加密 - 为保证安全,通过检查一次性代码验证 %s。 - 为保证安全,亲自或使用其他通信方式验证。 + 为保证安全,请核对一次性代码以验证 %s。 + 为保证安全,请当面验证,或者使用其他通讯方式验证。 比较独特表情,确保它们以相同顺序出现。 与其他用户设备上显示的代码比较。 - 与此用户的消息端到端加密,无法被第三方读取。 - 您的新会话已验证。它可以访问您的加密消息,其他用户会将其视为可信。 + 与此用户的消息端对端加密,无法被第三方读取。 + 您的新会话已验证。它可以访问您的加密消息,其他用户会将其视为可信任。 交叉签名 交叉签名已启用 \n设备上的私钥。 交叉签名已启用 -\n密钥可信。 +\n密钥可信任。 \n私钥未知 交叉签名已启用。 -\n密钥不可信 +\n密钥未信任 交叉签名未启用 - 您的服务器管理员已默认禁用私有聊天室和私聊消息端到端加密。 + 您的服务器管理员已默认禁用私有聊天室和私聊消息端对端加密。 激活会话 显示全部会话 管理会话 登出此会话 加密信息不可用 此会话对安全消息可信因为您已验证它: - 验证此会话以将其标记为可信,并授予其访问加密消息的权限。如果您未登录此会话,则您的帐户可能已被盗: + 验证此会话以将其标记为可信,并授予其访问加密消息的权限。如果您未登录此会话,则您的账号可能已被盗: %d 个活动会话 @@ -1962,10 +1964,10 @@ 注意 无法获取会话 会话 - 可信 - 不可信 - 此会话对加密消息可信,%1$s (%2$s) 已验证它: - %1$s (%2$s) 使用新会话登入: + 可信任 + 未信任 + 此会话可信任,可以用于收发加密消息,因为 %1$s(%2$s)已验证了它: + %1$s (%2$s) 使用新会话登录: 在此用户信任此会话之前,发送到该会话和从该会话发送的消息均标有警告。或者,您可以手动进行验证。 初始化交叉签名 重置密钥 @@ -1976,7 +1978,7 @@ 到服务器的连接已丢失 飞行模式已打开 开发工具 - 账户数据 + 账号数据 %d 票 @@ -1985,13 +1987,13 @@ 已选选项 创建简单调查 - 使用恢复短语或密钥 + 使用恢复密语或密钥 如果您无法访问已有会话 - 新登入 + 新登录 无法在存储中找到秘密 - 输入秘密存储密码 - 注意: - 您应仅在可信设备上访问秘密存储 + 输入秘密存储密语 + 警告: + 您应仅在可信任的设备上访问秘密存储 移除… 您想要发送此附件到 %1$s 吗? @@ -2010,29 +2012,29 @@ 轻按以审核和验证 使用此会话验证新的会话,授权访问加密消息。 这不是我 - 您的账户可能已被盗用 + 您的账号可能已被盗用 如果您取消,您将无法在此设备上读取加密消息,其他用户不会信任它 如果您取消,您将无法在新设备上读取加密消息,其他用户不会信任它 如果您现在取消将不会验证 %1$s (%2$s)。在他们的用户个人档案中重新开始。 以下其中一项可能有风险: \n \n- 您的密码 -\n- 您的家庭服务器 +\n- 您的主服务器 \n- 此设备或其它设备 \n- 设备使用的网络连接 \n \n我们推荐您在设置中立即更换您的密码和恢复密钥。 通过设置验证您的设备。 验证已取消 - 恢复密码 + 恢复密语 消息密钥 - 账户密码 - 设置一个 %s + 账号密码 + 设置 %s 生成消息密钥 确认 %s 输入您的 %s 以继续。 再次输入您的 %s 确认。 - 不要使用您的账户密码。 + 不要使用您的账号密码。 输入只有你知道的安全口令,用于保护服务器上的秘密。 这可能会花费数秒,请耐心等待。 设置恢复。 @@ -2042,7 +2044,7 @@ 完成 使用此 %1$s 作为安全网以防您忘记您的 %2$s。 发布创建的身份密钥 - 从密码生成安全密钥 + 从密语生成安全密钥 正在定义 SSSS 默认密钥 正在同步主密钥 正在同步用户密钥 @@ -2059,14 +2061,14 @@ 跳至已读回执 事件被聊天室管理员调整,理由:%1$s 密钥已是最新! - 保护与解锁已加密信息并信任%s。 + 保护与解锁已加密消息并信任 %s。 保存到优盘或者备份盘 复制到您的个人云存储 您无法在移动设备上执行此操作 - 设置恢复密码让您能够保护和解锁加密信息并信任设备。 + 设置恢复密语让您能够保护和解锁加密信息并信任设备。 \n \n如果您不希望设置文本密码,那么生成密钥亦可。 - 设置恢复密码让您能够保护和解锁加密信息并信任设备。 + 设置恢复密语让您能够保护和解锁加密信息并信任设备。 如果您现在取消,那么当您失去登录权限时也会丢失加密的信息和数据。 \n \n您也可以通过设置菜单来建立保护备份以及管理您的密钥。 @@ -2089,7 +2091,7 @@ 按事件设置通知重要性 以纯文本形式发送消息,而不将其解释为 markdown 用户名和/或密码不正确。输入的密码以空格开头或结尾,请检查。 - 此帐户已停用。 + 此账号已停用。 消息… 加密升级可用 启用交叉签名 @@ -2097,27 +2099,27 @@ 输入您的 %s 以继续 使用文件 输入 %s - 恢复密码 + 恢复密语 无效的恢复密钥 请输入恢复密钥 检查备份密钥 检查备份密钥 (%s) 获取曲线密钥 - 从密码生成 SSSS 密钥 - 从密码生成 SSSS 密钥 (%s) + 从密语生成 SSSS 密钥 + 从密语生成 SSSS 密钥(%s) 从恢复密钥生成 SSSS 密钥 正在在 SSSS 中保存密钥备份秘密 %1$s (%2$s) - 输入您的密钥备份密码以继续。 + 输入您的密钥备份密语以继续。 使用您的密钥备份恢复密钥 - 不知道您的密钥备份密码,您可以 %s。 + 不知道您的密钥备份密语,您可以 %s。 密钥备份恢复密钥 阻止应用内屏幕截图 启用此设置添加 FLAG_SECURE 到所有活动。重启应用使更改生效。 媒体文件已添加到相册 无法添加媒体文件到相册 无法保存媒体文件 - 选择新的账户密码… + 选择新的账号密码… 在你的其他设备上使用最新的${app_name} 网页版、${app_name} 桌面版、${app_name} iOS 版、${app_name} 安卓版,或其他能够交叉签名的 Matrix 客户端 ${app_name} Web \n${app_name} Desktop @@ -2134,14 +2136,14 @@ 访问安全存储失败 未加密 由未验证设备加密 - 查看您的登入位置 - 验证您的全部会话确保您的账户和消息安全 - 验证访问您的账户的新登录:%1$s + 查看您的登录位置 + 验证您的全部会话确保您的账号和消息安全 + 验证访问您的账号的新登录:%1$s 使用文本手动验证 验证登录 使用表情交互式验证 通过从您的其他会话验证此登录确认您的身份,授权它访问您的加密消息。 - 标记为可信 + 标记为信任 请选择用户名。 请选择密码。 仔细检查此链接 @@ -2165,13 +2167,13 @@ 打开 %s 条款 是否从身份服务器 %s 断开? 身份服务器已过期。${app_name} 仅支持 API V2。 - 无法执行此操作。家庭服务器已过期。 + 无法执行此操作。主服务器已过期。 请先配置身份服务器。 请先在设置中接受身份服务器的条款。 为了您的隐私,${app_name} 仅支持发送用户电子邮件和电话号码的哈希值。 关联失败。 - 此标识符无当前关联。 - 您的家庭服务器 (%1$s) 建议使用 %2$s 作为您的身份服务器 + 目前与此标识符没有关联。 + 您的主服务器(%1$s)建议使用 %2$s 作为您的身份服务器 使用 %1$s 或者,您可以输入任何其他身份服务器 URL 输入身份服务器 URL @@ -2185,14 +2187,14 @@ 启动相机 设置安全备份 安全备份 - 通过在您的服务器上备份加密密钥防止丢失对加密消息和数据的访问。 + 通过在您的服务器上备份加密密钥,防止失去对加密信息和数据的访问。 设置 使用安全密钥 生成安全密钥存储在安全的地方如密码管理器或保险箱。 使用安全口令 输入仅有您知道的安全口令,生成备份用的密钥。 保存您的安全密钥 - 将您的安全密钥存储在安全的地方如密码管理器或保险箱。 + 将您的安全密钥存储在安全的地方,例如密码管理器或保险箱。 设置安全口令 输入只有您知道的安全口令,用于保护您的服务器上的秘密。 安全口令 @@ -2205,19 +2207,19 @@ 您无法访问此消息 正在等待此消息,可能会花费一些时间 无法解密 - 由于端到端加密,您可能需要等待某人的消息到达因为加密密钥未正确发送给您。 + 由于端对端加密,您可能需要等待某人的消息到达因为加密密钥未正确发送给您。 您无法访问此消息因为您已屏蔽此发送者 - 您无法访问此消息因为您的会话不被发送者信任 + 您无法访问此消息,因为您的会话不被发送者信任 您无法访问此消息因为发送者有意不发送密钥 正在等待加密历史 - Riot 现在是 Element! - 我们兴奋地宣布我们改名了!您的应用已经是最新的并且您已登入您的帐户。 + Riot 现已成为 Element! + 我们很高兴地宣布我们改名了!您的应用已经更新到最新版本,并且您已登录您的账号。 明白了 了解更多 将恢复密钥保存到 - 从我的电话簿添加 - 您的电话簿是空的 - 电话簿 + 从我的通讯录添加 + 您的通讯录是空的 + 通讯录 搜索我的联系人 正在获取您的联系人… 您的通讯录是空的 @@ -2247,14 +2249,14 @@ 发起音频会议 会议使用 Jitsi 安全与许可政策。您的会议进行期间当前聊天室内的所有人将看到加入邀请。 您无法呼叫您自己 - 您无法呼叫您自己,请等待参与者接受邀请 - 添加小部件失败 - 移除小部件失败 + 您无法与自己通话,请等待参与者接受邀请 + 添加挂件失败 + 移除挂件失败 成功导入 %1$d/%2$d 个密钥。 管理集成 - 无活动小部件 + 无活动挂件 聊天室已创建,但由于以下原因一些邀请尚未发送: \n \n%s @@ -2267,14 +2269,14 @@ 注意!登出前最后一次尝试! 错误次数过多,您已被登出 此电话号码已定义。 - 您的帐户尚未添加电话号码 + 您的账号尚未添加电话号码 电子邮件地址 - 您的账户尚未添加电子邮件 + 您的账号尚未添加电子邮件 电话号码 移除 %s? 请确认您已点击我们向您发送的电子邮件中的链接。 电子邮件和电话号码 - 管理链接到您的 Matrix 账户的电子邮件和电话号码 + 管理链接到您的 Matrix 账号的电子邮件和电话号码 代码 请使用国际格式(电话号码必须以 ‘+’ 开始) 通过验证此登录确认您的身份,授权它访问加密信息。 @@ -2290,7 +2292,7 @@ 机器人按钮 回应:%s 验证结果 - 是否删除类型 %1$s 的账户数据? + 是否删除类型 %1$s 的账号数据? \n \n小心使用,它可能导致意外行为。 链接格式不正确 @@ -2313,17 +2315,17 @@ 如果您重置一切 仅当没有其他设备可用来验证此设备时,才执行此操作。 重置一切 - 忘记或丢失了所有的回复选项?重置一切 + 忘记或丢失了所有的恢复选项?重置一切 您已加入。 %s 已加入。 - 此聊天室的消息是端到端加密的。 + 此聊天室的消息是端对端加密的。 离开 设置 - 此处的消息是端到端加密的, + 此处的消息已被端对端加密。 \n -\n您的消息已用锁保护并且只有您和收件人拥有唯一的钥匙解锁它们。 - 此处的消息不是端到端加密。 - 此家庭服务器正在运行较旧版本。要求您的家庭服务器管理员升级。您可以继续,但一些功能可能无法正确工作。 +\n您的消息受加密保护,并且只有您和消息接收者拥有唯一解密密钥。 + 此处的消息未经端对端加密。 + 此主服务器正在运行较旧版本。要求您的主服务器管理员升级。您可以继续,但一些功能可能无法正确工作。 您仅发出此邀请。 %1$s 仅发出此邀请。 在加密聊天室显示完整历史 @@ -2361,7 +2363,7 @@ 聊天室地址 查看和管理此聊天室的地址,以及它在聊天室目录中的可见性。 聊天室地址 - 聊天室访问 + 聊天室访问权限 改成谁都可以阅读历史只会应用于此聊天室未来的消息。已经存在的历史消息的可见性将不会改变。 发送密钥共享请求历史记录 取消发布 @@ -2423,7 +2425,8 @@ 聊天室话题(可选) 聊天室名称 此聊天室无法预览。您想加入吗? - 此聊天室当前不可访问。请稍候重试,或向聊天室管理员询问以检查您是否拥有访问权限。 + 目前无法访问此聊天室。 +\n请稍候重试,或询问聊天室管理员以确认您是否拥有访问权限。 无法获取当前聊天室目录可见性(%1$s)。 是否将此聊天室发布至 %1$s 的聊天室目录中? 取消发布此地址 @@ -2449,7 +2452,7 @@ 启用聊天室加密 更改聊天室主地址 更改聊天室头像 - 修改小部件 + 修改挂件 通知每个人 移除其他人发送的消息 封禁用户 @@ -2471,4 +2474,105 @@ 未验证,缺少有效验证凭证 返回 系统默认 + • 匹配 %s 的服务器已被屏蔽。 + • IP 地址匹配的服务器已被屏蔽。 + • IP 地址匹配的服务器已被允许。 + • 匹配 %s 的服务器已被屏蔽。 + • 匹配 %s 的服务器已被允许。 + 已勾选 + 已选中 + 活跃通话(%1$s) + + %1$d 暂停了通话 + + 需要重新验证 + 删除失败的消息 + 您确定要取消发送消息吗? + 消息发送失败 + 删除未发送的消息 + 您确定要删除此聊天室中所有未发送的消息吗? + 失败 + 状态事件已发送! + 事件已发送! + 事件格式错误 + 已发送 + 正在发送 + 事件内容 + 无内容 + 事件内容 + 类型 + 发送自定义状态事件 + 编辑内容 + 状态事件 + 发送状态事件 + 发送自定义事件 + 开发者工具 + 视频 + 验证失败 + 用户 + 回拨 + %1$s 发起了通话 + 您发起了通话 + + %d 个条目 + + 不是有效的 Matrix 二维码 + 扫描二维码 + 添加人员 + 邀请朋友 + 服务器版本 + 服务器名称 + 切换 + 初始化同步: +\n正在下载数据… + 初始化同步: +\n正在等待服务器响应… + 空聊天室(曾为 %s) + + %1$s,%2$s,%3$s 和 %4$d 位其他成员 + + %1$s,%2$s,%3$s 和 %4$s + %1$s,%2$s 和 %3$s + %1$s 发起了视频会议 + 您发起了视频会议 + %1$s 启用了视频会议 + 您启用了视频会议 + %1$s 修改了视频会议 + 您修改了视频会议 + 设置头像 + 聊天室设置 + 聊天室版本 + 放弃修改 + 拨号键盘 + Matrix 链接 + 转移 + 连接 + 删除头像 + 修改头像 + 图片 + 关闭 Emoji 选择器 + 打开 Emoji 选择器 + 可信信任等级 + 警告信任等级 + 默认信任等级 + 你修改了此聊天室的服务器访问控制列表。 + %s 修改了此聊天室的服务器访问控制列表。 + %s 为此聊天室设置了服务器访问控制列表。 + 您为此聊天室设置了服务器访问控制列表。 + 在 Matrix 上查找联系人 + 用户尚未同意条款。 + 分享此二维码,其他人扫描后即可添加您,并开始聊天。 + 我的二维码 + 分享我的二维码 + 消息类型缺失 + 检查聊天室状态 + 查看已读回执 + 关闭通知 + 无声通知 + 有声通知 + 此聊天室有未发送的草稿 + 有些消息未被发送 + 从文件中导入密钥 + 发生错误,消息未能发送 + 状态键 \ No newline at end of file diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index 842700b6eb..f9345b146f 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -2610,4 +2610,15 @@ \n正在下載資料…… 初始同步: \n正在等待伺服器回應…… + 您確定您想要刪除此聊天室中所有未傳送的訊息嗎? + 刪除未傳送的訊息 + 訊息傳送失敗 + 您想要取消傳送訊息嗎? + 刪除所有失敗的訊息 + 失敗 + 已傳送 + 正在傳送 + 顯示聊天室目錄中的所有聊天室,包含有明確內容的聊天室。 + 顯示帶有明確內容的聊天室 + 聊天室目錄 \ No newline at end of file