From 70ad270f266b577e99d652de95c5684438a378e7 Mon Sep 17 00:00:00 2001 From: "Auri B. P" Date: Thu, 11 Mar 2021 20:29:55 +0000 Subject: [PATCH 001/249] Translated using Weblate (Catalan) Currently translated at 100.0% (2362 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ca/ --- vector/src/main/res/values-ca/strings.xml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) 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 From 116ed9b4a85bfd05461a430d73d8390ca7da0ea0 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Thu, 11 Mar 2021 19:09:26 +0000 Subject: [PATCH 002/249] Translated using Weblate (Czech) Currently translated at 100.0% (2362 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- vector/src/main/res/values-cs/strings.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index 03b733be05..341300cbf9 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -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 From a00a6d3fb5c9a740f80e7e2e5e85f21751864076 Mon Sep 17 00:00:00 2001 From: tiptoptom Date: Thu, 11 Mar 2021 20:52:40 +0000 Subject: [PATCH 003/249] Translated using Weblate (German) Currently translated at 99.5% (2351 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- vector/src/main/res/values-de/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 437c517574..a343090949 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -2722,4 +2722,5 @@ \nLade Daten herunter… Erste Synchronisation: \nWarte auf Serverantwort… + Gesendet \ No newline at end of file From db9933f2a1112f6e8f8fe7d3a9f570546e748e03 Mon Sep 17 00:00:00 2001 From: libexus Date: Thu, 11 Mar 2021 20:50:54 +0000 Subject: [PATCH 004/249] Translated using Weblate (German) Currently translated at 99.5% (2351 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- vector/src/main/res/values-de/strings.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index a343090949..1e9adff2d1 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -348,7 +348,7 @@ Keine Ergebnisse Räume - Raum-Verzeichnis + Raumverzeichnis Keine Räume Keine öffentl. Räume verfügbar @@ -2723,4 +2723,5 @@ Erste Synchronisation: \nWarte auf Serverantwort… Gesendet + Raumverzeichnis \ No newline at end of file From deabaf2e34f9146d41b0c6186cbf56ffb0d57363 Mon Sep 17 00:00:00 2001 From: "@a2sc:matrix.org" Date: Thu, 11 Mar 2021 20:02:42 +0000 Subject: [PATCH 005/249] Translated using Weblate (German) Currently translated at 99.5% (2351 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- vector/src/main/res/values-de/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 1e9adff2d1..6c646775a7 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -2724,4 +2724,5 @@ \nWarte auf Serverantwort… Gesendet Raumverzeichnis + Wechseln \ No newline at end of file From 840ee66903b25d60e370c677448a6a4b162438f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Thu, 11 Mar 2021 18:40:40 +0000 Subject: [PATCH 006/249] Translated using Weblate (Estonian) Currently translated at 100.0% (2362 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- vector/src/main/res/values-et/strings.xml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) 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 From 77f2ceaef913d35701cba40b4de619ab6606c933 Mon Sep 17 00:00:00 2001 From: Louis Raymond Date: Fri, 12 Mar 2021 08:07:27 +0000 Subject: [PATCH 007/249] Translated using Weblate (Indonesian) Currently translated at 33.5% (792 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/id/ --- vector/src/main/res/values-in/strings.xml | 318 +++++----------------- 1 file changed, 72 insertions(+), 246 deletions(-) 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 From 35ff4cc7cd32e38b12570bb6d73fdf0d0d38b80a Mon Sep 17 00:00:00 2001 From: random Date: Thu, 11 Mar 2021 09:57:56 +0000 Subject: [PATCH 008/249] Translated using Weblate (Italian) Currently translated at 100.0% (2362 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/it/ --- vector/src/main/res/values-it/strings.xml | 66 +++++++++++++++++++++++ 1 file changed, 66 insertions(+) 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 From 1346d232e683bfde440ed52f693136eff85b4e2a Mon Sep 17 00:00:00 2001 From: Marcelo Filho Date: Thu, 11 Mar 2021 19:47:00 +0000 Subject: [PATCH 009/249] Translated using Weblate (Portuguese (Brazil)) Currently translated at 98.4% (2325 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index d346cbff34..a2f930a272 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -2675,4 +2675,30 @@ 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… \ No newline at end of file From f8391f07b40d1258bdefe5678ac4498e6193c7f2 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Thu, 11 Mar 2021 21:32:45 +0000 Subject: [PATCH 010/249] Translated using Weblate (Swedish) Currently translated at 100.0% (2362 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sv/ --- vector/src/main/res/values-sv/strings.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 From 2c0a0a8e5a53c92c696b2a42143bf6d65bb15bc7 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 11 Mar 2021 03:17:14 +0000 Subject: [PATCH 011/249] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2362 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- vector/src/main/res/values-zh-rTW/strings.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 From 6e757f88b6224f617eea67233bfc267cc8cd8849 Mon Sep 17 00:00:00 2001 From: Marcelo Filho Date: Thu, 11 Mar 2021 19:38:05 +0000 Subject: [PATCH 012/249] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (13 of 13 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/pt_BR/ --- fastlane/metadata/android/pt-BR/changelogs/40101000.txt | 2 ++ fastlane/metadata/android/pt-BR/changelogs/40101010.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/pt-BR/changelogs/40101000.txt create mode 100644 fastlane/metadata/android/pt-BR/changelogs/40101010.txt 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 From e7d78863974c864f5fb7a996b1549f2af3912359 Mon Sep 17 00:00:00 2001 From: LinAGKar Date: Thu, 11 Mar 2021 15:36:31 +0000 Subject: [PATCH 013/249] Translated using Weblate (Swedish) Currently translated at 100.0% (13 of 13 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/sv/ --- fastlane/metadata/android/sv/changelogs/40101010.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/sv/changelogs/40101010.txt 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 From 6148b2647fe0d75c177748e57efcba0e56c010cf Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Thu, 11 Mar 2021 01:02:47 +0000 Subject: [PATCH 014/249] Translated using Weblate (Ukrainian) Currently translated at 100.0% (13 of 13 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/uk/ --- fastlane/metadata/android/uk/changelogs/40101010.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/uk/changelogs/40101010.txt 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 From 7651df1228c0bed7a954b35393a48059435d7bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Thu, 11 Mar 2021 17:07:01 +0000 Subject: [PATCH 015/249] Translated using Weblate (Estonian) Currently translated at 100.0% (13 of 13 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/et/ --- fastlane/metadata/android/et/changelogs/40101010.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/et/changelogs/40101010.txt 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 From 3ad6c7a18b4f6b22bc497c24eeea4ea97c32d0aa Mon Sep 17 00:00:00 2001 From: random Date: Thu, 11 Mar 2021 09:36:24 +0000 Subject: [PATCH 016/249] Translated using Weblate (Italian) Currently translated at 100.0% (13 of 13 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/it/ --- fastlane/metadata/android/it/changelogs/40101000.txt | 2 ++ fastlane/metadata/android/it/changelogs/40101010.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/it/changelogs/40101000.txt create mode 100644 fastlane/metadata/android/it/changelogs/40101010.txt 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 From 7edb45d9b71c692968cc72dd0f0338ee4560d3fa Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Thu, 11 Mar 2021 02:25:36 +0000 Subject: [PATCH 017/249] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (13 of 13 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/zh_Hant/ --- fastlane/metadata/android/zh-Hant/changelogs/40101010.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/zh-Hant/changelogs/40101010.txt 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 From 8f0319a1382f25d589804904d7b12007b1c6e9f2 Mon Sep 17 00:00:00 2001 From: zeritti Date: Thu, 11 Mar 2021 22:47:16 +0000 Subject: [PATCH 018/249] Translated using Weblate (Czech) Currently translated at 100.0% (13 of 13 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/cs/ --- fastlane/metadata/android/cs/changelogs/40101010.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/cs/changelogs/40101010.txt 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 From 2d5a3b71e2f7bcc19684e833feac5cc2265ba6c2 Mon Sep 17 00:00:00 2001 From: Sven Grewe Date: Sat, 13 Mar 2021 14:52:36 +0000 Subject: [PATCH 019/249] Translated using Weblate (German) Currently translated at 99.7% (2355 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- vector/src/main/res/values-de/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 6c646775a7..71cdb25616 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -2725,4 +2725,7 @@ 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\? \ No newline at end of file From a5c113137a44502a84fe326e22f422ab1e0ca778 Mon Sep 17 00:00:00 2001 From: libexus Date: Sat, 13 Mar 2021 14:00:14 +0000 Subject: [PATCH 020/249] Translated using Weblate (German) Currently translated at 99.7% (2355 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- vector/src/main/res/values-de/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 71cdb25616..51fe3937fd 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -2728,4 +2728,5 @@ 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 \ No newline at end of file From 04a27aa41854bc078e46296315f060337d9c81d6 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Sun, 14 Mar 2021 02:45:49 +0000 Subject: [PATCH 021/249] Translated using Weblate (Ukrainian) Currently translated at 63.2% (1494 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- vector/src/main/res/values-uk/strings.xml | 71 ++++++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 09aafac703..a9ad29004f 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -869,8 +869,8 @@ Запит ключа відправлений. Запит відправлений Вібрація при згадуванні користувача - Деактивація аккаунта - Деактивувати мій аккаунт + Деактивація облікового запису + Деактивувати мій обліковий запис Конфіденційність сповіщень Надати дозвіл Вибрати другий варіант @@ -1702,4 +1702,71 @@ Перевірте цей сеанс, підтвердивши, що на екрані партнера з’являються ці емоджі Перевірте цей сеанс, щоб позначити його надійним. Довірені сеанси партнерів дають вам додаткову впевненість під час використання наскрізно зашифрованих повідомлень. Перевірте цей сеанс, щоб позначити його надійним та надати йому доступ до зашифрованих повідомлень. Якщо ви не входили в цей сеанс, ваш обліковий запис може бути зламано: + Назва або 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 + Змінювати дозволи + Змінювати назву кімнати + Змінювати видимість історії + Вмикати шифрування кімнати + Змінювати основну адресу кімнати + Змінювати аватар кімнати + Змінювати віджети + Сповіщати всіх + Вилучати повідомлення, надіслані іншими + Блокувати користувачів + Викидати користувачів + Змінювати налаштування + Запрошувати користувачів + Надсилати повідомлення + Типова роль + У вас немає дозволу на оновлення ролей, необхідних для зміни різних частин кімнати + Виберіть ролі, необхідні для зміни різних частин кімнати + Дозволи + Перегляд та оновлення ролей, необхідних для зміни різних частин кімнати. + Залишити кімнату + Залишити кімнату + Залишити + Залишити поточну конференцію та перейти до іншої\? + Це не загальнодоступна кімната. Ви не зможете знову приєднатися без запрошення. + У вас немає дозволу на ввімкнення шифрування в цій кімнаті. + Дозволи кімнати \ No newline at end of file From 256ddc1831408a8723fb119bbedfd416347be78e Mon Sep 17 00:00:00 2001 From: "Auri B. P" Date: Sat, 13 Mar 2021 09:53:04 +0000 Subject: [PATCH 022/249] Translated using Weblate (Catalan) Currently translated at 100.0% (13 of 13 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/ca/ --- fastlane/metadata/android/ca/changelogs/40101010.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 fastlane/metadata/android/ca/changelogs/40101010.txt 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 From 9bfc1d09cd05209514b47c0f658e1e1d8685c174 Mon Sep 17 00:00:00 2001 From: Graeme Power Date: Sun, 14 Mar 2021 12:44:08 +0000 Subject: [PATCH 023/249] Added translation using Weblate (Irish) --- vector/src/main/res/values-ga/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 vector/src/main/res/values-ga/strings.xml 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..a6b3daec93 --- /dev/null +++ b/vector/src/main/res/values-ga/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From e8015bfbd4f8e24faa0fd21917b150defcb17a82 Mon Sep 17 00:00:00 2001 From: oogm Date: Mon, 15 Mar 2021 00:19:04 +0100 Subject: [PATCH 024/249] Update emoji_picker_datasource.json to Unicode 13.1 and dynamically filter reactions by renderability --- CHANGES.md | 1 + .../reactions/data/EmojiDataSourceTest.kt | 8 ++--- .../reactions/data/EmojiDataSource.kt | 29 ++++++++++++++++--- .../main/res/raw/emoji_picker_datasource.json | 2 +- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e4ff049550..c6f98124d1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -41,6 +41,7 @@ Improvements 🙌: - Sending is now queuing by room and not uniquely to the session - Improve Snackbar duration (#2929) - Improve sending message state (#2937) + - Update reactions to Unicode 13.1 (#1481) Bugfix 🐛: - Try to fix crash about UrlPreview (#2640) diff --git a/vector/src/androidTest/java/im/vector/app/features/reactions/data/EmojiDataSourceTest.kt b/vector/src/androidTest/java/im/vector/app/features/reactions/data/EmojiDataSourceTest.kt index 8959416445..79090c42dd 100644 --- a/vector/src/androidTest/java/im/vector/app/features/reactions/data/EmojiDataSourceTest.kt +++ b/vector/src/androidTest/java/im/vector/app/features/reactions/data/EmojiDataSourceTest.kt @@ -42,17 +42,15 @@ class EmojiDataSourceTest : InstrumentedTest { @Test fun checkNumberOfResult() { val emojiDataSource = EmojiDataSource(context().resources) - - assertEquals("Wrong number of emojis", 1545, emojiDataSource.rawData.emojis.size) - assertEquals("Wrong number of categories", 8, emojiDataSource.rawData.categories.size) - assertEquals("Wrong number of aliases", 57, emojiDataSource.rawData.aliases.size) + assertTrue("Wrong number of emojis", emojiDataSource.rawData.emojis.size >= 500) + assertTrue("Wrong number of categories", emojiDataSource.rawData.categories.size >= 8) } @Test fun searchTestEmptySearch() { val emojiDataSource = EmojiDataSource(context().resources) - assertEquals("Empty search should return 1545 results", 1545, emojiDataSource.filterWith("").size) + assertTrue("Empty search should return at least 500 results", emojiDataSource.filterWith("").size >= 500) } @Test diff --git a/vector/src/main/java/im/vector/app/features/reactions/data/EmojiDataSource.kt b/vector/src/main/java/im/vector/app/features/reactions/data/EmojiDataSource.kt index 891f083644..97f85ea1f5 100644 --- a/vector/src/main/java/im/vector/app/features/reactions/data/EmojiDataSource.kt +++ b/vector/src/main/java/im/vector/app/features/reactions/data/EmojiDataSource.kt @@ -16,6 +16,8 @@ package im.vector.app.features.reactions.data import android.content.res.Resources +import android.graphics.Paint +import androidx.core.graphics.PaintCompat import com.squareup.moshi.Moshi import im.vector.app.R import javax.inject.Inject @@ -25,6 +27,7 @@ import javax.inject.Singleton class EmojiDataSource @Inject constructor( resources: Resources ) { + private val paint = Paint() val rawData = resources.openRawResource(R.raw.emoji_picker_datasource) .use { input -> Moshi.Builder() @@ -34,18 +37,32 @@ class EmojiDataSource @Inject constructor( } ?.let { parsedRawData -> // Add key as a keyword, it will solve the issue that ":tada" is not available in completion + // Only add emojis to emojis/categories that can be rendered by the system parsedRawData.copy( emojis = mutableMapOf().apply { parsedRawData.emojis.keys.forEach { key -> val origin = parsedRawData.emojis[key] ?: return@forEach // Do not add keys containing '_' - if (origin.keywords.contains(key) || key.contains("_")) { - put(key, origin) - } else { - put(key, origin.copy(keywords = origin.keywords + key)) + if (isEmojiRenderable(origin.emoji)) { + if (origin.keywords.contains(key) || key.contains("_")) { + put(key, origin) + } else { + put(key, origin.copy(keywords = origin.keywords + key)) + } } } + }, + categories = mutableListOf().apply { + parsedRawData.categories.forEach { entry -> + add(EmojiCategory(entry.id, entry.name, mutableListOf().apply { + entry.emojis.forEach { e -> + if (isEmojiRenderable(parsedRawData.emojis[e]!!.emoji)) { + add(e) + } + } + })) + } } ) } @@ -53,6 +70,10 @@ class EmojiDataSource @Inject constructor( private val quickReactions = mutableListOf() + private fun isEmojiRenderable(emoji: String): Boolean { + return PaintCompat.hasGlyph(paint, emoji) + } + fun filterWith(query: String): List { val words = query.split("\\s".toRegex()) diff --git a/vector/src/main/res/raw/emoji_picker_datasource.json b/vector/src/main/res/raw/emoji_picker_datasource.json index 431b7d420c..bf33808f2f 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":"people","name":"Smileys & People","emojis":["grinning","grin","joy","rolling_on_the_floor_laughing","smiley","smile","sweat_smile","laughing","wink","blush","yum","sunglasses","heart_eyes","kissing_heart","kissing","kissing_smiling_eyes","kissing_closed_eyes","relaxed","slightly_smiling_face","hugging_face","star-struck","thinking_face","face_with_raised_eyebrow","neutral_face","expressionless","no_mouth","face_with_rolling_eyes","smirk","persevere","disappointed_relieved","open_mouth","zipper_mouth_face","hushed","sleepy","tired_face","sleeping","relieved","stuck_out_tongue","stuck_out_tongue_winking_eye","stuck_out_tongue_closed_eyes","drooling_face","unamused","sweat","pensive","confused","upside_down_face","money_mouth_face","astonished","white_frowning_face","slightly_frowning_face","confounded","disappointed","worried","triumph","cry","sob","frowning","anguished","fearful","weary","exploding_head","grimacing","cold_sweat","scream","flushed","zany_face","dizzy_face","rage","angry","face_with_symbols_on_mouth","mask","face_with_thermometer","face_with_head_bandage","nauseated_face","face_vomiting","sneezing_face","innocent","face_with_cowboy_hat","clown_face","lying_face","shushing_face","face_with_hand_over_mouth","face_with_monocle","nerd_face","smiling_imp","imp","japanese_ogre","japanese_goblin","skull","skull_and_crossbones","ghost","alien","space_invader","robot_face","hankey","smiley_cat","smile_cat","joy_cat","heart_eyes_cat","smirk_cat","kissing_cat","scream_cat","crying_cat_face","pouting_cat","see_no_evil","hear_no_evil","speak_no_evil","baby","child","boy","girl","adult","man","woman","older_adult","older_man","older_woman","male-doctor","female-doctor","male-student","female-student","male-teacher","female-teacher","male-judge","female-judge","male-farmer","female-farmer","male-cook","female-cook","male-mechanic","female-mechanic","male-factory-worker","female-factory-worker","male-office-worker","female-office-worker","male-scientist","female-scientist","male-technologist","female-technologist","male-singer","female-singer","male-artist","female-artist","male-pilot","female-pilot","male-astronaut","female-astronaut","male-firefighter","female-firefighter","cop","male-police-officer","female-police-officer","sleuth_or_spy","male-detective","female-detective","guardsman","male-guard","female-guard","construction_worker","male-construction-worker","female-construction-worker","prince","princess","man_with_turban","man-wearing-turban","woman-wearing-turban","man_with_gua_pi_mao","person_with_headscarf","bearded_person","person_with_blond_hair","blond-haired-man","blond-haired-woman","man_in_tuxedo","bride_with_veil","pregnant_woman","breast-feeding","angel","santa","mrs_claus","mage","female_mage","male_mage","fairy","female_fairy","male_fairy","vampire","female_vampire","male_vampire","merperson","mermaid","merman","elf","female_elf","male_elf","genie","female_genie","male_genie","zombie","female_zombie","male_zombie","person_frowning","man-frowning","woman-frowning","person_with_pouting_face","man-pouting","woman-pouting","no_good","man-gesturing-no","woman-gesturing-no","ok_woman","man-gesturing-ok","woman-gesturing-ok","information_desk_person","man-tipping-hand","woman-tipping-hand","raising_hand","man-raising-hand","woman-raising-hand","bow","man-bowing","woman-bowing","face_palm","man-facepalming","woman-facepalming","shrug","man-shrugging","woman-shrugging","massage","man-getting-massage","woman-getting-massage","haircut","man-getting-haircut","woman-getting-haircut","walking","man-walking","woman-walking","runner","man-running","woman-running","dancer","man_dancing","dancers","man-with-bunny-ears-partying","woman-with-bunny-ears-partying","person_in_steamy_room","woman_in_steamy_room","man_in_steamy_room","person_climbing","woman_climbing","man_climbing","person_in_lotus_position","woman_in_lotus_position","man_in_lotus_position","bath","sleeping_accommodation","man_in_business_suit_levitating","speaking_head_in_silhouette","bust_in_silhouette","busts_in_silhouette","fencer","horse_racing","skier","snowboarder","golfer","man-golfing","woman-golfing","surfer","man-surfing","woman-surfing","rowboat","man-rowing-boat","woman-rowing-boat","swimmer","man-swimming","woman-swimming","person_with_ball","man-bouncing-ball","woman-bouncing-ball","weight_lifter","man-lifting-weights","woman-lifting-weights","bicyclist","man-biking","woman-biking","mountain_bicyclist","man-mountain-biking","woman-mountain-biking","racing_car","racing_motorcycle","person_doing_cartwheel","man-cartwheeling","woman-cartwheeling","wrestlers","man-wrestling","woman-wrestling","water_polo","man-playing-water-polo","woman-playing-water-polo","handball","man-playing-handball","woman-playing-handball","juggling","man-juggling","woman-juggling","couple","two_men_holding_hands","two_women_holding_hands","couplekiss","woman-kiss-man","man-kiss-man","woman-kiss-woman","couple_with_heart","woman-heart-man","man-heart-man","woman-heart-woman","family","man-woman-boy","man-woman-girl","man-woman-girl-boy","man-woman-boy-boy","man-woman-girl-girl","man-man-boy","man-man-girl","man-man-girl-boy","man-man-boy-boy","man-man-girl-girl","woman-woman-boy","woman-woman-girl","woman-woman-girl-boy","woman-woman-boy-boy","woman-woman-girl-girl","man-boy","man-boy-boy","man-girl","man-girl-boy","man-girl-girl","woman-boy","woman-boy-boy","woman-girl","woman-girl-boy","woman-girl-girl","selfie","muscle","point_left","point_right","point_up","point_up_2","middle_finger","point_down","v","crossed_fingers","spock-hand","the_horns","call_me_hand","raised_hand_with_fingers_splayed","hand","ok_hand","+1","-1","fist","facepunch","left-facing_fist","right-facing_fist","raised_back_of_hand","wave","i_love_you_hand_sign","writing_hand","clap","open_hands","raised_hands","palms_up_together","pray","handshake","nail_care","ear","nose","footprints","eyes","eye","eye-in-speech-bubble","brain","tongue","lips","kiss","cupid","heart","heartbeat","broken_heart","two_hearts","sparkling_heart","heartpulse","blue_heart","green_heart","yellow_heart","orange_heart","purple_heart","black_heart","gift_heart","revolving_hearts","heart_decoration","heavy_heart_exclamation_mark_ornament","love_letter","zzz","anger","bomb","boom","sweat_drops","dash","dizzy","speech_balloon","left_speech_bubble","right_anger_bubble","thought_balloon","hole","eyeglasses","dark_sunglasses","necktie","shirt","jeans","scarf","gloves","coat","socks","dress","kimono","bikini","womans_clothes","purse","handbag","pouch","shopping_bags","school_satchel","mans_shoe","athletic_shoe","high_heel","sandal","boot","crown","womans_hat","tophat","mortar_board","billed_cap","helmet_with_white_cross","prayer_beads","lipstick","ring","gem"]},{"id":"nature","name":"Animals & Nature","emojis":["monkey_face","monkey","gorilla","dog","dog2","poodle","wolf","fox_face","cat","cat2","lion_face","tiger","tiger2","leopard","horse","racehorse","unicorn_face","zebra_face","deer","cow","ox","water_buffalo","cow2","pig","pig2","boar","pig_nose","ram","sheep","goat","dromedary_camel","camel","giraffe_face","elephant","rhinoceros","mouse","mouse2","rat","hamster","rabbit","rabbit2","chipmunk","hedgehog","bat","bear","koala","panda_face","feet","turkey","chicken","rooster","hatching_chick","baby_chick","hatched_chick","bird","penguin","dove_of_peace","eagle","duck","owl","frog","crocodile","turtle","lizard","snake","dragon_face","dragon","sauropod","t-rex","whale","whale2","dolphin","fish","tropical_fish","blowfish","shark","octopus","shell","crab","shrimp","squid","snail","butterfly","bug","ant","bee","beetle","cricket","spider","spider_web","scorpion","bouquet","cherry_blossom","white_flower","rosette","rose","wilted_flower","hibiscus","sunflower","blossom","tulip","seedling","evergreen_tree","deciduous_tree","palm_tree","cactus","ear_of_rice","herb","shamrock","four_leaf_clover","maple_leaf","fallen_leaf","leaves"]},{"id":"foods","name":"Food & Drink","emojis":["grapes","melon","watermelon","tangerine","lemon","banana","pineapple","apple","green_apple","pear","peach","cherries","strawberry","kiwifruit","tomato","coconut","avocado","eggplant","potato","carrot","corn","hot_pepper","cucumber","broccoli","mushroom","peanuts","chestnut","bread","croissant","baguette_bread","pretzel","pancakes","cheese_wedge","meat_on_bone","poultry_leg","cut_of_meat","bacon","hamburger","fries","pizza","hotdog","sandwich","taco","burrito","stuffed_flatbread","egg","fried_egg","shallow_pan_of_food","stew","bowl_with_spoon","green_salad","popcorn","canned_food","bento","rice_cracker","rice_ball","rice","curry","ramen","spaghetti","sweet_potato","oden","sushi","fried_shrimp","fish_cake","dango","dumpling","fortune_cookie","takeout_box","icecream","shaved_ice","ice_cream","doughnut","cookie","birthday","cake","pie","chocolate_bar","candy","lollipop","custard","honey_pot","baby_bottle","glass_of_milk","coffee","tea","sake","champagne","wine_glass","cocktail","tropical_drink","beer","beers","clinking_glasses","tumbler_glass","cup_with_straw","chopsticks","knife_fork_plate","fork_and_knife","spoon","hocho","amphora"]},{"id":"activity","name":"Activities","emojis":["jack_o_lantern","christmas_tree","fireworks","sparkler","sparkles","balloon","tada","confetti_ball","tanabata_tree","bamboo","dolls","flags","wind_chime","rice_scene","ribbon","gift","reminder_ribbon","admission_tickets","ticket","medal","trophy","sports_medal","first_place_medal","second_place_medal","third_place_medal","soccer","baseball","basketball","volleyball","football","rugby_football","tennis","8ball","bowling","cricket_bat_and_ball","field_hockey_stick_and_ball","ice_hockey_stick_and_puck","table_tennis_paddle_and_ball","badminton_racquet_and_shuttlecock","boxing_glove","martial_arts_uniform","goal_net","dart","golf","ice_skate","fishing_pole_and_fish","running_shirt_with_sash","ski","sled","curling_stone","video_game","joystick","game_die","spades","hearts","diamonds","clubs","black_joker","mahjong","flower_playing_cards"]},{"id":"places","name":"Travel & Places","emojis":["earth_africa","earth_americas","earth_asia","globe_with_meridians","world_map","japan","snow_capped_mountain","mountain","volcano","mount_fuji","camping","beach_with_umbrella","desert","desert_island","national_park","stadium","classical_building","building_construction","house_buildings","cityscape","derelict_house_building","house","house_with_garden","office","post_office","european_post_office","hospital","bank","hotel","love_hotel","convenience_store","school","department_store","factory","japanese_castle","european_castle","wedding","tokyo_tower","statue_of_liberty","church","mosque","synagogue","shinto_shrine","kaaba","fountain","tent","foggy","night_with_stars","sunrise_over_mountains","sunrise","city_sunset","city_sunrise","bridge_at_night","hotsprings","milky_way","carousel_horse","ferris_wheel","roller_coaster","barber","circus_tent","performing_arts","frame_with_picture","art","slot_machine","steam_locomotive","railway_car","bullettrain_side","bullettrain_front","train2","metro","light_rail","station","tram","monorail","mountain_railway","train","bus","oncoming_bus","trolleybus","minibus","ambulance","fire_engine","police_car","oncoming_police_car","taxi","oncoming_taxi","car","oncoming_automobile","blue_car","truck","articulated_lorry","tractor","bike","scooter","motor_scooter","busstop","motorway","railway_track","fuelpump","rotating_light","traffic_light","vertical_traffic_light","construction","octagonal_sign","anchor","boat","canoe","speedboat","passenger_ship","ferry","motor_boat","ship","airplane","small_airplane","airplane_departure","airplane_arriving","seat","helicopter","suspension_railway","mountain_cableway","aerial_tramway","satellite","rocket","flying_saucer","bellhop_bell","door","bed","couch_and_lamp","toilet","shower","bathtub","hourglass","hourglass_flowing_sand","watch","alarm_clock","stopwatch","timer_clock","mantelpiece_clock","clock12","clock1230","clock1","clock130","clock2","clock230","clock3","clock330","clock4","clock430","clock5","clock530","clock6","clock630","clock7","clock730","clock8","clock830","clock9","clock930","clock10","clock1030","clock11","clock1130","new_moon","waxing_crescent_moon","first_quarter_moon","moon","full_moon","waning_gibbous_moon","last_quarter_moon","waning_crescent_moon","crescent_moon","new_moon_with_face","first_quarter_moon_with_face","last_quarter_moon_with_face","thermometer","sunny","full_moon_with_face","sun_with_face","star","star2","stars","cloud","partly_sunny","thunder_cloud_and_rain","mostly_sunny","barely_sunny","partly_sunny_rain","rain_cloud","snow_cloud","lightning","tornado","fog","wind_blowing_face","cyclone","rainbow","closed_umbrella","umbrella","umbrella_with_rain_drops","umbrella_on_ground","zap","snowflake","snowman","snowman_without_snow","comet","fire","droplet","ocean"]},{"id":"objects","name":"Objects","emojis":["mute","speaker","sound","loud_sound","loudspeaker","mega","postal_horn","bell","no_bell","musical_score","musical_note","notes","studio_microphone","level_slider","control_knobs","microphone","headphones","radio","saxophone","guitar","musical_keyboard","trumpet","violin","drum_with_drumsticks","iphone","calling","phone","telephone_receiver","pager","fax","battery","electric_plug","computer","desktop_computer","printer","keyboard","three_button_mouse","trackball","minidisc","floppy_disk","cd","dvd","movie_camera","film_frames","film_projector","clapper","tv","camera","camera_with_flash","video_camera","vhs","mag","mag_right","microscope","telescope","satellite_antenna","candle","bulb","flashlight","izakaya_lantern","notebook_with_decorative_cover","closed_book","book","green_book","blue_book","orange_book","books","notebook","ledger","page_with_curl","scroll","page_facing_up","newspaper","rolled_up_newspaper","bookmark_tabs","bookmark","label","moneybag","yen","dollar","euro","pound","money_with_wings","credit_card","chart","currency_exchange","heavy_dollar_sign","email","e-mail","incoming_envelope","envelope_with_arrow","outbox_tray","inbox_tray","package","mailbox","mailbox_closed","mailbox_with_mail","mailbox_with_no_mail","postbox","ballot_box_with_ballot","pencil2","black_nib","lower_left_fountain_pen","lower_left_ballpoint_pen","lower_left_paintbrush","lower_left_crayon","memo","briefcase","file_folder","open_file_folder","card_index_dividers","date","calendar","spiral_note_pad","spiral_calendar_pad","card_index","chart_with_upwards_trend","chart_with_downwards_trend","bar_chart","clipboard","pushpin","round_pushpin","paperclip","linked_paperclips","straight_ruler","triangular_ruler","scissors","card_file_box","file_cabinet","wastebasket","lock","unlock","lock_with_ink_pen","closed_lock_with_key","key","old_key","hammer","pick","hammer_and_pick","hammer_and_wrench","dagger_knife","crossed_swords","gun","bow_and_arrow","shield","wrench","nut_and_bolt","gear","compression","alembic","scales","link","chains","syringe","pill","smoking","coffin","funeral_urn","moyai","oil_drum","crystal_ball","shopping_trolley"]},{"id":"symbols","name":"Symbols","emojis":["atm","put_litter_in_its_place","potable_water","wheelchair","mens","womens","restroom","baby_symbol","wc","passport_control","customs","baggage_claim","left_luggage","warning","children_crossing","no_entry","no_entry_sign","no_bicycles","no_smoking","do_not_litter","non-potable_water","no_pedestrians","no_mobile_phones","underage","radioactive_sign","biohazard_sign","arrow_up","arrow_upper_right","arrow_right","arrow_lower_right","arrow_down","arrow_lower_left","arrow_left","arrow_upper_left","arrow_up_down","left_right_arrow","leftwards_arrow_with_hook","arrow_right_hook","arrow_heading_up","arrow_heading_down","arrows_clockwise","arrows_counterclockwise","back","end","on","soon","top","place_of_worship","atom_symbol","om_symbol","star_of_david","wheel_of_dharma","yin_yang","latin_cross","orthodox_cross","star_and_crescent","peace_symbol","menorah_with_nine_branches","six_pointed_star","aries","taurus","gemini","cancer","leo","virgo","libra","scorpius","sagittarius","capricorn","aquarius","pisces","ophiuchus","twisted_rightwards_arrows","repeat","repeat_one","arrow_forward","fast_forward","black_right_pointing_double_triangle_with_vertical_bar","black_right_pointing_triangle_with_double_vertical_bar","arrow_backward","rewind","black_left_pointing_double_triangle_with_vertical_bar","arrow_up_small","arrow_double_up","arrow_down_small","arrow_double_down","double_vertical_bar","black_square_for_stop","black_circle_for_record","eject","cinema","low_brightness","high_brightness","signal_strength","vibration_mode","mobile_phone_off","female_sign","male_sign","medical_symbol","recycle","fleur_de_lis","trident","name_badge","beginner","o","white_check_mark","ballot_box_with_check","heavy_check_mark","heavy_multiplication_x","x","negative_squared_cross_mark","heavy_plus_sign","heavy_minus_sign","heavy_division_sign","curly_loop","loop","part_alternation_mark","eight_spoked_asterisk","eight_pointed_black_star","sparkle","bangbang","interrobang","question","grey_question","grey_exclamation","exclamation","wavy_dash","copyright","registered","tm","hash","keycap_star","zero","one","two","three","four","five","six","seven","eight","nine","keycap_ten","100","capital_abcd","abcd","1234","symbols","abc","a","ab","b","cl","cool","free","information_source","id","m","new","ng","o2","ok","parking","sos","up","vs","koko","sa","u6708","u6709","u6307","ideograph_advantage","u5272","u7121","u7981","accept","u7533","u5408","u7a7a","congratulations","secret","u55b6","u6e80","black_small_square","white_small_square","white_medium_square","black_medium_square","white_medium_small_square","black_medium_small_square","black_large_square","white_large_square","large_orange_diamond","large_blue_diamond","small_orange_diamond","small_blue_diamond","small_red_triangle","small_red_triangle_down","diamond_shape_with_a_dot_inside","radio_button","black_square_button","white_square_button","white_circle","black_circle","red_circle","large_blue_circle"]},{"id":"flags","name":"Flags","emojis":["checkered_flag","cn","crossed_flags","de","es","flag-ac","flag-ad","flag-ae","flag-af","flag-ag","flag-ai","flag-al","flag-am","flag-ao","flag-aq","flag-ar","flag-as","flag-at","flag-au","flag-aw","flag-ax","flag-az","flag-ba","flag-bb","flag-bd","flag-be","flag-bf","flag-bg","flag-bh","flag-bi","flag-bj","flag-bm","flag-bn","flag-bo","flag-br","flag-bs","flag-bt","flag-bv","flag-bw","flag-by","flag-bz","flag-ca","flag-cc","flag-cd","flag-cf","flag-cg","flag-ch","flag-ci","flag-ck","flag-cl","flag-cm","flag-co","flag-cp","flag-cr","flag-cu","flag-cv","flag-cw","flag-cx","flag-cy","flag-cz","flag-dj","flag-dk","flag-dm","flag-do","flag-dz","flag-ec","flag-ee","flag-eg","flag-england","flag-er","flag-et","flag-eu","flag-fi","flag-fj","flag-fm","flag-fo","flag-ga","flag-gd","flag-ge","flag-gg","flag-gh","flag-gi","flag-gl","flag-gm","flag-gn","flag-gq","flag-gr","flag-gt","flag-gu","flag-gw","flag-gy","flag-hk","flag-hm","flag-hn","flag-hr","flag-ht","flag-hu","flag-ic","flag-id","flag-ie","flag-il","flag-im","flag-in","flag-io","flag-iq","flag-ir","flag-is","flag-je","flag-jm","flag-jo","flag-ke","flag-kg","flag-kh","flag-ki","flag-km","flag-kn","flag-kp","flag-kw","flag-ky","flag-kz","flag-la","flag-lb","flag-lc","flag-li","flag-lk","flag-lr","flag-ls","flag-lt","flag-lu","flag-lv","flag-ly","flag-ma","flag-mc","flag-md","flag-me","flag-mg","flag-mh","flag-mk","flag-ml","flag-mm","flag-mn","flag-mo","flag-mp","flag-mr","flag-ms","flag-mt","flag-mu","flag-mv","flag-mw","flag-mx","flag-my","flag-mz","flag-na","flag-ne","flag-nf","flag-ng","flag-ni","flag-nl","flag-no","flag-np","flag-nr","flag-nu","flag-nz","flag-om","flag-pa","flag-pe","flag-pf","flag-pg","flag-ph","flag-pk","flag-pl","flag-pn","flag-pr","flag-ps","flag-pt","flag-pw","flag-py","flag-qa","flag-ro","flag-rs","flag-rw","flag-sa","flag-sb","flag-sc","flag-scotland","flag-sd","flag-se","flag-sg","flag-sh","flag-si","flag-sj","flag-sk","flag-sl","flag-sm","flag-sn","flag-so","flag-sr","flag-ss","flag-st","flag-sv","flag-sx","flag-sy","flag-sz","flag-ta","flag-tc","flag-td","flag-tg","flag-th","flag-tj","flag-tk","flag-tl","flag-tm","flag-tn","flag-to","flag-tr","flag-tt","flag-tv","flag-tw","flag-tz","flag-ua","flag-ug","flag-um","flag-uy","flag-uz","flag-va","flag-vc","flag-ve","flag-vg","flag-vi","flag-vn","flag-vu","flag-wales","flag-ws","flag-ye","flag-za","flag-zm","flag-zw","fr","gb","it","jp","kr","rainbow-flag","ru","triangular_flag_on_post","us","waving_black_flag","waving_white_flag"]}],"emojis":{"100":{"a":"Hundred Points Symbol","b":"1F4AF","j":["score","perfect","numbers","century","exam","quiz","test","pass","hundred"],"k":[25,26]},"1234":{"a":"Input Symbol for Numbers","b":"1F522","j":["numbers","blue-square"],"k":[27,36]},"monkey_face":{"a":"Monkey Face","b":"1F435","j":["animal","nature","circus"],"k":[13,31],"l":[":o)"]},"grinning":{"a":"Grinning Face","b":"1F600","j":["face","smile","happy","joy",":D","grin"],"k":[30,24],"m":":D"},"earth_africa":{"a":"Earth Globe Europe-Africa","b":"1F30D","j":["globe","world","international"],"k":[6,5]},"checkered_flag":{"a":"Chequered Flag","b":"1F3C1","j":["contest","finishline","race","gokart"],"k":[9,27]},"mute":{"a":"Speaker with Cancellation Stroke","b":"1F507","j":["sound","volume","silence","quiet"],"k":[27,9]},"jack_o_lantern":{"a":"Jack-O-Lantern","b":"1F383","j":["halloween","light","pumpkin","creepy","fall"],"k":[8,17]},"atm":{"a":"Automated Teller Machine","b":"1F3E7","j":["money","sales","cash","blue-square","payment","bank"],"k":[12,4]},"grapes":{"a":"Grapes","b":"1F347","j":["fruit","food","wine"],"k":[7,9]},"earth_americas":{"a":"Earth Globe Americas","b":"1F30E","j":["globe","world","USA","international"],"k":[6,6]},"grin":{"a":"Grinning Face with Smiling Eyes","b":"1F601","j":["face","happy","smile","joy","kawaii"],"k":[30,25]},"melon":{"a":"Melon","b":"1F348","j":["fruit","nature","food"],"k":[7,10]},"triangular_flag_on_post":{"a":"Triangular Flag on Post","b":"1F6A9","j":["mark","milestone","place"],"k":[35,14]},"monkey":{"a":"Monkey","b":"1F412","j":["animal","nature","banana","circus"],"k":[12,48]},"christmas_tree":{"a":"Christmas Tree","b":"1F384","j":["festival","vacation","december","xmas","celebration"],"k":[8,18]},"put_litter_in_its_place":{"a":"Put Litter in Its Place Symbol","b":"1F6AE","j":["blue-square","sign","human","info"],"k":[35,19]},"speaker":{"a":"Speaker","b":"1F508","j":["sound","volume","silence","broadcast"],"k":[27,10]},"earth_asia":{"a":"Earth Globe Asia-Australia","b":"1F30F","j":["globe","world","east","international"],"k":[6,7]},"crossed_flags":{"a":"Crossed Flags","b":"1F38C","j":["japanese","nation","country","border"],"k":[8,31]},"joy":{"a":"Face with Tears of Joy","b":"1F602","j":["face","cry","tears","weep","happy","happytears","haha"],"k":[30,26]},"sound":{"a":"Speaker with One Sound Wave","b":"1F509","j":["volume","speaker","broadcast"],"k":[27,11]},"watermelon":{"a":"Watermelon","b":"1F349","j":["fruit","food","picnic","summer"],"k":[7,11]},"gorilla":{"a":"Gorilla","b":"1F98D","j":["animal","nature","circus"],"k":[42,37],"o":9},"fireworks":{"a":"Fireworks","b":"1F386","j":["photo","festival","carnival","congratulations"],"k":[8,25]},"potable_water":{"a":"Potable Water Symbol","b":"1F6B0","j":["blue-square","liquid","restroom","cleaning","faucet"],"k":[35,21]},"wheelchair":{"a":"Wheelchair Symbol","b":"267F","j":["blue-square","disabled","a11y","accessibility"],"k":[48,10],"o":4},"rolling_on_the_floor_laughing":{"a":"Rolling on the Floor Laughing","b":"1F923","k":[38,26],"o":9},"loud_sound":{"a":"Speaker with Three Sound Waves","b":"1F50A","j":["volume","noise","noisy","speaker","broadcast"],"k":[27,12]},"waving_black_flag":{"a":"Waving Black Flag","b":"1F3F4","k":[12,19],"o":7},"tangerine":{"a":"Tangerine","b":"1F34A","j":["food","fruit","nature","orange"],"k":[7,12]},"dog":{"a":"Dog Face","b":"1F436","j":["animal","friend","nature","woof","puppy","pet","faithful"],"k":[13,32]},"sparkler":{"a":"Firework Sparkler","b":"1F387","j":["stars","night","shine"],"k":[8,26]},"globe_with_meridians":{"a":"Globe with Meridians","b":"1F310","j":["earth","international","world","internet","interweb","i18n"],"k":[6,8]},"smiley":{"a":"Smiling Face with Open Mouth","b":"1F603","j":["face","happy","joy","haha",":D",":)","smile","funny"],"k":[30,27],"l":["=)","=-)"],"m":":)"},"loudspeaker":{"a":"Public Address Loudspeaker","b":"1F4E2","j":["volume","sound"],"k":[26,25]},"sparkles":{"a":"Sparkles","b":"2728","j":["stars","shine","shiny","cool","awesome","good","magic"],"k":[49,48]},"dog2":{"a":"Dog","b":"1F415","j":["animal","nature","friend","doge","pet","faithful"],"k":[12,51]},"waving_white_flag":{"a":"Waving White Flag","b":"1F3F3-FE0F","c":"1F3F3","k":[12,15],"o":7},"world_map":{"a":"World Map","b":"1F5FA-FE0F","c":"1F5FA","j":["location","direction"],"k":[30,18],"o":7},"lemon":{"a":"Lemon","b":"1F34B","j":["fruit","nature"],"k":[7,13]},"mens":{"a":"Mens Symbol","b":"1F6B9","j":["toilet","restroom","wc","blue-square","gender","male"],"k":[36,29]},"womens":{"a":"Womens Symbol","b":"1F6BA","j":["purple-square","woman","female","toilet","loo","restroom","gender"],"k":[36,30]},"rainbow-flag":{"a":"Rainbow Flag","b":"1F3F3-FE0F-200D-1F308","c":"1F3F3-200D-1F308","k":[12,14],"o":7},"smile":{"a":"Smiling Face with Open Mouth and Smiling Eyes","b":"1F604","j":["face","happy","joy","funny","haha","laugh","like",":D",":)"],"k":[30,28],"l":["C:","c:",":D",":-D"],"m":":)"},"banana":{"a":"Banana","b":"1F34C","j":["fruit","food","monkey"],"k":[7,14]},"mega":{"a":"Cheering Megaphone","b":"1F4E3","j":["sound","speaker","volume"],"k":[26,26]},"japan":{"a":"Silhouette of Japan","b":"1F5FE","j":["nation","country","japanese","asia"],"k":[30,22]},"poodle":{"a":"Poodle","b":"1F429","j":["dog","animal","101","nature","pet"],"k":[13,19]},"balloon":{"a":"Balloon","b":"1F388","j":["party","celebration","birthday","circus"],"k":[8,27]},"flag-ac":{"a":"Ascension Island Flag","b":"1F1E6-1F1E8","k":[0,31]},"sweat_smile":{"a":"Smiling Face with Open Mouth and Cold Sweat","b":"1F605","j":["face","hot","happy","laugh","sweat","smile","relief"],"k":[30,29]},"pineapple":{"a":"Pineapple","b":"1F34D","j":["fruit","nature","food"],"k":[7,15]},"restroom":{"a":"Restroom","b":"1F6BB","j":["blue-square","toilet","refresh","wc","gender"],"k":[36,31]},"postal_horn":{"a":"Postal Horn","b":"1F4EF","j":["instrument","music"],"k":[26,38]},"wolf":{"a":"Wolf Face","b":"1F43A","j":["animal","nature","wild"],"k":[13,36]},"tada":{"a":"Party Popper","b":"1F389","j":["party","congratulations","birthday","magic","circus","celebration"],"k":[8,28]},"snow_capped_mountain":{"a":"Snow Capped Mountain","b":"1F3D4-FE0F","c":"1F3D4","k":[11,37],"o":7},"laughing":{"a":"Smiling Face with Open Mouth and Tightly-Closed Eyes","b":"1F606","j":["happy","joy","lol","satisfied","haha","face","glad","XD","laugh"],"k":[30,30],"l":[":>",":->"],"n":["satisfied"]},"apple":{"a":"Red Apple","b":"1F34E","j":["fruit","mac","school"],"k":[7,16]},"flag-ad":{"a":"Andorra Flag","b":"1F1E6-1F1E9","k":[0,32]},"fox_face":{"a":"Fox Face","b":"1F98A","j":["animal","nature","face"],"k":[42,34],"o":9},"confetti_ball":{"a":"Confetti Ball","b":"1F38A","j":["festival","party","birthday","circus"],"k":[8,29]},"bell":{"a":"Bell","b":"1F514","j":["sound","notification","christmas","xmas","chime"],"k":[27,22]},"mountain":{"a":"Mountain","b":"26F0-FE0F","c":"26F0","j":["photo","nature","environment"],"k":[48,38],"o":5},"baby_symbol":{"a":"Baby Symbol","b":"1F6BC","j":["orange-square","child"],"k":[36,32]},"wc":{"a":"Water Closet","b":"1F6BE","j":["toilet","restroom","blue-square"],"k":[36,34]},"wink":{"a":"Winking Face","b":"1F609","j":["face","happy","mischievous","secret",";)","smile","eye"],"k":[30,33],"l":[";)",";-)"],"m":";)"},"no_bell":{"a":"Bell with Cancellation Stroke","b":"1F515","j":["sound","volume","mute","quiet","silent"],"k":[27,23]},"green_apple":{"a":"Green Apple","b":"1F34F","j":["fruit","nature"],"k":[7,17]},"tanabata_tree":{"a":"Tanabata Tree","b":"1F38B","j":["plant","nature","branch","summer"],"k":[8,30]},"flag-ae":{"a":"United Arab Emirates Flag","b":"1F1E6-1F1EA","k":[0,33]},"volcano":{"a":"Volcano","b":"1F30B","j":["photo","nature","disaster"],"k":[6,3]},"cat":{"a":"Cat Face","b":"1F431","j":["animal","meow","nature","pet","kitten"],"k":[13,27]},"flag-af":{"a":"Afghanistan Flag","b":"1F1E6-1F1EB","k":[0,34]},"musical_score":{"a":"Musical Score","b":"1F3BC","j":["treble","clef","compose"],"k":[9,22]},"blush":{"a":"Smiling Face with Smiling Eyes","b":"1F60A","j":["face","smile","happy","flushed","crush","embarrassed","shy","joy"],"k":[30,34],"m":":)"},"pear":{"a":"Pear","b":"1F350","j":["fruit","nature","food"],"k":[7,18]},"bamboo":{"a":"Pine Decoration","b":"1F38D","j":["plant","nature","vegetable","panda","pine_decoration"],"k":[8,32]},"passport_control":{"a":"Passport Control","b":"1F6C2","j":["custom","blue-square"],"k":[36,43]},"mount_fuji":{"a":"Mount Fuji","b":"1F5FB","j":["photo","mountain","nature","japanese"],"k":[30,19]},"cat2":{"a":"Cat","b":"1F408","j":["animal","meow","pet","cats"],"k":[12,38]},"musical_note":{"a":"Musical Note","b":"1F3B5","j":["score","tone","sound"],"k":[9,15]},"dolls":{"a":"Japanese Dolls","b":"1F38E","j":["japanese","toy","kimono"],"k":[8,33]},"lion_face":{"a":"Lion Face","b":"1F981","k":[42,25],"o":8},"camping":{"a":"Camping","b":"1F3D5-FE0F","c":"1F3D5","j":["photo","outdoors","tent"],"k":[11,38],"o":7},"flag-ag":{"a":"Antigua & Barbuda Flag","b":"1F1E6-1F1EC","k":[0,35]},"customs":{"a":"Customs","b":"1F6C3","j":["passport","border","blue-square"],"k":[36,44]},"yum":{"a":"Face Savouring Delicious Food","b":"1F60B","j":["happy","joy","tongue","smile","face","silly","yummy","nom","delicious","savouring"],"k":[30,35]},"peach":{"a":"Peach","b":"1F351","j":["fruit","nature","food"],"k":[7,19]},"tiger":{"a":"Tiger Face","b":"1F42F","j":["animal","cat","danger","wild","nature","roar"],"k":[13,25]},"notes":{"a":"Multiple Musical Notes","b":"1F3B6","j":["music","score"],"k":[9,16]},"flags":{"a":"Carp Streamer","b":"1F38F","j":["fish","japanese","koinobori","carp","banner"],"k":[8,34]},"beach_with_umbrella":{"a":"Beach with Umbrella","b":"1F3D6-FE0F","c":"1F3D6","k":[11,39],"o":7},"cherries":{"a":"Cherries","b":"1F352","j":["food","fruit"],"k":[7,20]},"flag-ai":{"a":"Anguilla Flag","b":"1F1E6-1F1EE","k":[0,36]},"baggage_claim":{"a":"Baggage Claim","b":"1F6C4","j":["blue-square","airport","transport"],"k":[36,45]},"sunglasses":{"a":"Smiling Face with Sunglasses","b":"1F60E","j":["face","cool","smile","summer","beach","sunglass"],"k":[30,38],"l":["8)"]},"left_luggage":{"a":"Left Luggage","b":"1F6C5","j":["blue-square","travel"],"k":[36,46]},"wind_chime":{"a":"Wind Chime","b":"1F390","j":["nature","ding","spring","bell"],"k":[8,35]},"strawberry":{"a":"Strawberry","b":"1F353","j":["fruit","food","nature"],"k":[7,21]},"desert":{"a":"Desert","b":"1F3DC-FE0F","c":"1F3DC","j":["photo","warm","saharah"],"k":[11,45],"o":7},"studio_microphone":{"a":"Studio Microphone","b":"1F399-FE0F","c":"1F399","j":["sing","recording","artist","talkshow"],"k":[8,41],"o":7},"flag-al":{"a":"Albania Flag","b":"1F1E6-1F1F1","k":[0,37]},"tiger2":{"a":"Tiger","b":"1F405","j":["animal","nature","roar"],"k":[12,35]},"heart_eyes":{"a":"Smiling Face with Heart-Shaped Eyes","b":"1F60D","j":["face","love","like","affection","valentines","infatuation","crush","heart"],"k":[30,37]},"desert_island":{"a":"Desert Island","b":"1F3DD-FE0F","c":"1F3DD","j":["photo","tropical","mojito"],"k":[11,46],"o":7},"kiwifruit":{"a":"Kiwifruit","b":"1F95D","k":[42,9],"o":9},"rice_scene":{"a":"Moon Viewing Ceremony","b":"1F391","j":["photo","japan","asia","tsukimi"],"k":[8,36]},"kissing_heart":{"a":"Face Throwing a Kiss","b":"1F618","j":["face","love","like","affection","valentines","infatuation","kiss"],"k":[30,48],"l":[":*",":-*"]},"warning":{"a":"Warning Sign","b":"26A0-FE0F","c":"26A0","j":["exclamation","wip","alert","error","problem","issue"],"k":[48,20],"o":4},"flag-am":{"a":"Armenia Flag","b":"1F1E6-1F1F2","k":[0,38]},"leopard":{"a":"Leopard","b":"1F406","j":["animal","nature"],"k":[12,36]},"level_slider":{"a":"Level Slider","b":"1F39A-FE0F","c":"1F39A","j":["scale"],"k":[8,42],"o":7},"horse":{"a":"Horse Face","b":"1F434","j":["animal","brown","nature"],"k":[13,30]},"children_crossing":{"a":"Children Crossing","b":"1F6B8","j":["school","warning","danger","sign","driving","yellow-diamond"],"k":[36,28]},"ribbon":{"a":"Ribbon","b":"1F380","j":["decoration","pink","girl","bowtie"],"k":[8,14]},"national_park":{"a":"National Park","b":"1F3DE-FE0F","c":"1F3DE","j":["photo","environment","nature"],"k":[11,47],"o":7},"control_knobs":{"a":"Control Knobs","b":"1F39B-FE0F","c":"1F39B","j":["dial"],"k":[8,43],"o":7},"kissing":{"a":"Kissing Face","b":"1F617","j":["love","like","face","3","valentines","infatuation","kiss"],"k":[30,47]},"tomato":{"a":"Tomato","b":"1F345","j":["fruit","vegetable","nature","food"],"k":[7,7]},"flag-ao":{"a":"Angola Flag","b":"1F1E6-1F1F4","k":[0,39]},"stadium":{"a":"Stadium","b":"1F3DF-FE0F","c":"1F3DF","j":["photo","place","sports","concert","venue"],"k":[11,48],"o":7},"flag-aq":{"a":"Antarctica Flag","b":"1F1E6-1F1F6","k":[0,40]},"gift":{"a":"Wrapped Present","b":"1F381","j":["present","birthday","christmas","xmas"],"k":[8,15]},"no_entry":{"a":"No Entry","b":"26D4","j":["limit","security","privacy","bad","denied","stop","circle"],"k":[48,35],"o":5},"kissing_smiling_eyes":{"a":"Kissing Face with Smiling Eyes","b":"1F619","j":["face","affection","valentines","infatuation","kiss"],"k":[30,49]},"coconut":{"a":"Coconut","b":"1F965","k":[42,17],"o":10},"racehorse":{"a":"Horse","b":"1F40E","j":["animal","gamble","luck"],"k":[12,44]},"microphone":{"a":"Microphone","b":"1F3A4","j":["sound","music","PA","sing","talkshow"],"k":[8,50]},"classical_building":{"a":"Classical Building","b":"1F3DB-FE0F","c":"1F3DB","j":["art","culture","history"],"k":[11,44],"o":7},"no_entry_sign":{"a":"No Entry Sign","b":"1F6AB","j":["forbid","stop","limit","denied","disallow","circle"],"k":[35,16]},"reminder_ribbon":{"a":"Reminder Ribbon","b":"1F397-FE0F","c":"1F397","j":["sports","cause","support","awareness"],"k":[8,40],"o":7},"kissing_closed_eyes":{"a":"Kissing Face with Closed Eyes","b":"1F61A","j":["face","love","like","affection","valentines","infatuation","kiss"],"k":[30,50]},"unicorn_face":{"a":"Unicorn Face","b":"1F984","k":[42,28],"o":8},"flag-ar":{"a":"Argentina Flag","b":"1F1E6-1F1F7","k":[0,41]},"headphones":{"a":"Headphone","b":"1F3A7","j":["music","score","gadgets"],"k":[9,1]},"avocado":{"a":"Avocado","b":"1F951","j":["fruit","food"],"k":[41,49],"o":9},"relaxed":{"a":"White Smiling Face","b":"263A-FE0F","c":"263A","j":["face","blush","massage","happiness"],"k":[47,41],"o":1},"zebra_face":{"a":"Zebra Face","b":"1F993","k":[42,43],"o":10},"eggplant":{"a":"Aubergine","b":"1F346","j":["vegetable","nature","food","aubergine"],"k":[7,8]},"radio":{"a":"Radio","b":"1F4FB","j":["communication","music","podcast","program"],"k":[26,50]},"building_construction":{"a":"Building Construction","b":"1F3D7-FE0F","c":"1F3D7","j":["wip","working","progress"],"k":[11,40],"o":7},"flag-as":{"a":"American Samoa Flag","b":"1F1E6-1F1F8","k":[0,42]},"admission_tickets":{"a":"Admission Tickets","b":"1F39F-FE0F","c":"1F39F","k":[8,45],"o":7},"no_bicycles":{"a":"No Bicycles","b":"1F6B3","j":["cyclist","prohibited","circle"],"k":[35,24]},"no_smoking":{"a":"No Smoking Symbol","b":"1F6AD","j":["cigarette","blue-square","smell","smoke"],"k":[35,18]},"slightly_smiling_face":{"a":"Slightly Smiling Face","b":"1F642","j":["face","smile"],"k":[31,38],"l":[":)","(:",":-)"],"o":7},"flag-at":{"a":"Austria Flag","b":"1F1E6-1F1F9","k":[0,43]},"ticket":{"a":"Ticket","b":"1F3AB","j":["event","concert","pass"],"k":[9,5]},"saxophone":{"a":"Saxophone","b":"1F3B7","j":["music","instrument","jazz","blues"],"k":[9,17]},"deer":{"a":"Deer","b":"1F98C","j":["animal","nature","horns","venison"],"k":[42,36],"o":9},"house_buildings":{"a":"House Buildings","b":"1F3D8-FE0F","c":"1F3D8","k":[11,41],"o":7},"potato":{"a":"Potato","b":"1F954","j":["food","tuber","vegatable","starch"],"k":[42,0],"o":9},"guitar":{"a":"Guitar","b":"1F3B8","j":["music","instrument"],"k":[9,18]},"carrot":{"a":"Carrot","b":"1F955","j":["vegetable","food","orange"],"k":[42,1],"o":9},"cityscape":{"a":"Cityscape","b":"1F3D9-FE0F","c":"1F3D9","j":["photo","night life","urban"],"k":[11,42],"o":7},"flag-au":{"a":"Australia Flag","b":"1F1E6-1F1FA","k":[0,44]},"do_not_litter":{"a":"Do Not Litter Symbol","b":"1F6AF","j":["trash","bin","garbage","circle"],"k":[35,20]},"hugging_face":{"a":"Hugging Face","b":"1F917","k":[37,31],"o":8},"cow":{"a":"Cow Face","b":"1F42E","j":["beef","ox","animal","nature","moo","milk"],"k":[13,24]},"medal":{"a":"Medal","b":"1F396-FE0F","c":"1F396","k":[8,39],"o":7},"musical_keyboard":{"a":"Musical Keyboard","b":"1F3B9","j":["piano","instrument","compose"],"k":[9,19]},"corn":{"a":"Ear of Maize","b":"1F33D","j":["food","vegetable","plant"],"k":[6,51]},"derelict_house_building":{"a":"Derelict House Building","b":"1F3DA-FE0F","c":"1F3DA","k":[11,43],"o":7},"non-potable_water":{"a":"Non-Potable Water Symbol","b":"1F6B1","j":["drink","faucet","tap","circle"],"k":[35,22]},"trophy":{"a":"Trophy","b":"1F3C6","j":["win","award","contest","place","ftw","ceremony"],"k":[10,19]},"flag-aw":{"a":"Aruba Flag","b":"1F1E6-1F1FC","k":[0,45]},"star-struck":{"a":"Grinning Face with Star Eyes","b":"1F929","k":[38,49],"n":["grinning_face_with_star_eyes"],"o":10},"ox":{"a":"Ox","b":"1F402","j":["animal","cow","beef"],"k":[12,32]},"trumpet":{"a":"Trumpet","b":"1F3BA","j":["music","brass"],"k":[9,20]},"hot_pepper":{"a":"Hot Pepper","b":"1F336-FE0F","c":"1F336","j":["food","spicy","chilli","chili"],"k":[6,44],"o":7},"sports_medal":{"a":"Sports Medal","b":"1F3C5","k":[10,18],"o":7},"flag-ax":{"a":"Åland Islands Flag","b":"1F1E6-1F1FD","k":[0,46]},"water_buffalo":{"a":"Water Buffalo","b":"1F403","j":["animal","nature","ox","cow"],"k":[12,33]},"no_pedestrians":{"a":"No Pedestrians","b":"1F6B7","j":["rules","crossing","walking","circle"],"k":[36,27]},"thinking_face":{"a":"Thinking Face","b":"1F914","k":[37,28],"o":8},"house":{"a":"House Building","b":"1F3E0","j":["building","home"],"k":[11,49]},"no_mobile_phones":{"a":"No Mobile Phones","b":"1F4F5","j":["iphone","mute","circle"],"k":[26,44]},"flag-az":{"a":"Azerbaijan Flag","b":"1F1E6-1F1FF","k":[0,47]},"first_place_medal":{"a":"First Place Medal","b":"1F947","k":[41,42],"o":9},"house_with_garden":{"a":"House with Garden","b":"1F3E1","j":["home","plant","nature"],"k":[11,50]},"violin":{"a":"Violin","b":"1F3BB","j":["music","instrument","orchestra","symphony"],"k":[9,21]},"face_with_raised_eyebrow":{"a":"Face with One Eyebrow Raised","b":"1F928","k":[38,48],"n":["face_with_one_eyebrow_raised"],"o":10},"cucumber":{"a":"Cucumber","b":"1F952","j":["fruit","food","pickle"],"k":[41,50],"o":9},"cow2":{"a":"Cow","b":"1F404","j":["beef","ox","animal","nature","moo","milk"],"k":[12,34]},"flag-ba":{"a":"Bosnia & Herzegovina Flag","b":"1F1E7-1F1E6","k":[0,48]},"pig":{"a":"Pig Face","b":"1F437","j":["animal","oink","nature"],"k":[13,33]},"drum_with_drumsticks":{"a":"Drum with Drumsticks","b":"1F941","k":[41,37],"o":9},"underage":{"a":"No One Under Eighteen Symbol","b":"1F51E","j":["18","drink","pub","night","minor","circle"],"k":[27,32]},"broccoli":{"a":"Broccoli","b":"1F966","k":[42,18],"o":10},"office":{"a":"Office Building","b":"1F3E2","j":["building","bureau","work"],"k":[11,51]},"second_place_medal":{"a":"Second Place Medal","b":"1F948","k":[41,43],"o":9},"neutral_face":{"a":"Neutral Face","b":"1F610","j":["indifference","meh",":|","neutral"],"k":[30,40],"l":[":|",":-|"]},"third_place_medal":{"a":"Third Place Medal","b":"1F949","k":[41,44],"o":9},"mushroom":{"a":"Mushroom","b":"1F344","j":["plant","vegetable"],"k":[7,6]},"flag-bb":{"a":"Barbados Flag","b":"1F1E7-1F1E7","k":[0,49]},"radioactive_sign":{"a":"Radioactive Sign","b":"2622-FE0F","c":"2622","k":[47,33],"o":1},"pig2":{"a":"Pig","b":"1F416","j":["animal","nature"],"k":[13,0]},"expressionless":{"a":"Expressionless Face","b":"1F611","j":["face","indifferent","-_-","meh","deadpan"],"k":[30,41]},"iphone":{"a":"Mobile Phone","b":"1F4F1","j":["technology","apple","gadgets","dial"],"k":[26,40]},"post_office":{"a":"Japanese Post Office","b":"1F3E3","j":["building","envelope","communication"],"k":[12,0]},"european_post_office":{"a":"European Post Office","b":"1F3E4","j":["building","email"],"k":[12,1]},"soccer":{"a":"Soccer Ball","b":"26BD","j":["sports","football"],"k":[48,26],"o":5},"boar":{"a":"Boar","b":"1F417","j":["animal","nature"],"k":[13,1]},"peanuts":{"a":"Peanuts","b":"1F95C","j":["food","nut"],"k":[42,8],"o":9},"calling":{"a":"Mobile Phone with Rightwards Arrow at Left","b":"1F4F2","j":["iphone","incoming"],"k":[26,41]},"biohazard_sign":{"a":"Biohazard Sign","b":"2623-FE0F","c":"2623","k":[47,34],"o":1},"flag-bd":{"a":"Bangladesh Flag","b":"1F1E7-1F1E9","k":[0,50]},"no_mouth":{"a":"Face Without Mouth","b":"1F636","j":["face","hellokitty"],"k":[31,26]},"face_with_rolling_eyes":{"a":"Face with Rolling Eyes","b":"1F644","k":[31,40],"o":8},"phone":{"a":"Black Telephone","b":"260E-FE0F","c":"260E","j":["technology","communication","dial","telephone"],"k":[47,21],"n":["telephone"],"o":1},"pig_nose":{"a":"Pig Nose","b":"1F43D","j":["animal","oink"],"k":[13,39]},"chestnut":{"a":"Chestnut","b":"1F330","j":["food","squirrel"],"k":[6,38]},"arrow_up":{"a":"Upwards Black Arrow","b":"2B06-FE0F","c":"2B06","j":["blue-square","continue","top","direction"],"k":[50,18],"o":4},"hospital":{"a":"Hospital","b":"1F3E5","j":["building","health","surgery","doctor"],"k":[12,2]},"flag-be":{"a":"Belgium Flag","b":"1F1E7-1F1EA","k":[0,51]},"baseball":{"a":"Baseball","b":"26BE","j":["sports","balls"],"k":[48,27],"o":5},"smirk":{"a":"Smirking Face","b":"1F60F","j":["face","smile","mean","prank","smug","sarcasm"],"k":[30,39]},"arrow_upper_right":{"a":"North East Arrow","b":"2197-FE0F","c":"2197","j":["blue-square","point","direction","diagonal","northeast"],"k":[46,36],"o":1},"flag-bf":{"a":"Burkina Faso Flag","b":"1F1E7-1F1EB","k":[1,0]},"basketball":{"a":"Basketball and Hoop","b":"1F3C0","j":["sports","balls","NBA"],"k":[9,26]},"ram":{"a":"Ram","b":"1F40F","j":["animal","sheep","nature"],"k":[12,45]},"bank":{"a":"Bank","b":"1F3E6","j":["building","money","sales","cash","business","enterprise"],"k":[12,3]},"bread":{"a":"Bread","b":"1F35E","j":["food","wheat","breakfast","toast"],"k":[7,32]},"telephone_receiver":{"a":"Telephone Receiver","b":"1F4DE","j":["technology","communication","dial"],"k":[26,21]},"croissant":{"a":"Croissant","b":"1F950","j":["food","bread","french"],"k":[41,48],"o":9},"pager":{"a":"Pager","b":"1F4DF","j":["bbcall","oldschool","90s"],"k":[26,22]},"sheep":{"a":"Sheep","b":"1F411","j":["animal","nature","wool","shipit"],"k":[12,47]},"arrow_right":{"a":"Black Rightwards Arrow","b":"27A1-FE0F","c":"27A1","j":["blue-square","next"],"k":[50,12],"o":1},"persevere":{"a":"Persevering Face","b":"1F623","j":["face","sick","no","upset","oops"],"k":[31,7]},"flag-bg":{"a":"Bulgaria Flag","b":"1F1E7-1F1EC","k":[1,1]},"volleyball":{"a":"Volleyball","b":"1F3D0","j":["sports","balls"],"k":[11,33],"o":8},"hotel":{"a":"Hotel","b":"1F3E8","j":["building","accomodation","checkin"],"k":[12,5]},"arrow_lower_right":{"a":"South East Arrow","b":"2198-FE0F","c":"2198","j":["blue-square","direction","diagonal","southeast"],"k":[46,37],"o":1},"goat":{"a":"Goat","b":"1F410","j":["animal","nature"],"k":[12,46]},"flag-bh":{"a":"Bahrain Flag","b":"1F1E7-1F1ED","k":[1,2]},"love_hotel":{"a":"Love Hotel","b":"1F3E9","j":["like","affection","dating"],"k":[12,6]},"disappointed_relieved":{"a":"Disappointed but Relieved Face","b":"1F625","j":["face","phew","sweat","nervous"],"k":[31,9]},"baguette_bread":{"a":"Baguette Bread","b":"1F956","j":["food","bread","french"],"k":[42,2],"o":9},"football":{"a":"American Football","b":"1F3C8","j":["sports","balls","NFL"],"k":[10,26]},"fax":{"a":"Fax Machine","b":"1F4E0","j":["communication","technology"],"k":[26,23]},"convenience_store":{"a":"Convenience Store","b":"1F3EA","j":["building","shopping","groceries"],"k":[12,7]},"dromedary_camel":{"a":"Dromedary Camel","b":"1F42A","j":["animal","hot","desert","hump"],"k":[13,20]},"arrow_down":{"a":"Downwards Black Arrow","b":"2B07-FE0F","c":"2B07","j":["blue-square","direction","bottom"],"k":[50,19],"o":4},"battery":{"a":"Battery","b":"1F50B","j":["power","energy","sustain"],"k":[27,13]},"rugby_football":{"a":"Rugby Football","b":"1F3C9","j":["sports","team"],"k":[10,27]},"pretzel":{"a":"Pretzel","b":"1F968","k":[42,20],"o":10},"open_mouth":{"a":"Face with Open Mouth","b":"1F62E","j":["face","surprise","impressed","wow","whoa",":O"],"k":[31,18],"l":[":o",":-o",":O",":-O"]},"flag-bi":{"a":"Burundi Flag","b":"1F1E7-1F1EE","k":[1,3]},"flag-bj":{"a":"Benin Flag","b":"1F1E7-1F1EF","k":[1,4]},"pancakes":{"a":"Pancakes","b":"1F95E","j":["food","breakfast","flapjacks","hotcakes"],"k":[42,10],"o":9},"school":{"a":"School","b":"1F3EB","j":["building","student","education","learn","teach"],"k":[12,8]},"tennis":{"a":"Tennis Racquet and Ball","b":"1F3BE","j":["sports","balls","green"],"k":[9,24]},"zipper_mouth_face":{"a":"Zipper-Mouth Face","b":"1F910","j":["face","sealed","zipper","secret"],"k":[37,24],"o":8},"camel":{"a":"Bactrian Camel","b":"1F42B","j":["animal","nature","hot","desert","hump"],"k":[13,21]},"arrow_lower_left":{"a":"South West Arrow","b":"2199-FE0F","c":"2199","j":["blue-square","direction","diagonal","southwest"],"k":[46,38],"o":1},"electric_plug":{"a":"Electric Plug","b":"1F50C","j":["charger","power"],"k":[27,14]},"cheese_wedge":{"a":"Cheese Wedge","b":"1F9C0","k":[42,48],"o":8},"hushed":{"a":"Hushed Face","b":"1F62F","j":["face","woo","shh"],"k":[31,19]},"computer":{"a":"Personal Computer","b":"1F4BB","j":["technology","laptop","screen","display","monitor"],"k":[25,38]},"giraffe_face":{"a":"Giraffe Face","b":"1F992","k":[42,42],"o":10},"8ball":{"a":"Billiards","b":"1F3B1","j":["pool","hobby","game","luck","magic"],"k":[9,11]},"arrow_left":{"a":"Leftwards Black Arrow","b":"2B05-FE0F","c":"2B05","j":["blue-square","previous","back"],"k":[50,17],"o":4},"department_store":{"a":"Department Store","b":"1F3EC","j":["building","shopping","mall"],"k":[12,9]},"meat_on_bone":{"a":"Meat on Bone","b":"1F356","j":["good","food","drumstick"],"k":[7,24]},"arrow_upper_left":{"a":"North West Arrow","b":"2196-FE0F","c":"2196","j":["blue-square","point","direction","diagonal","northwest"],"k":[46,35],"o":1},"flag-bm":{"a":"Bermuda Flag","b":"1F1E7-1F1F2","k":[1,6]},"sleepy":{"a":"Sleepy Face","b":"1F62A","j":["face","tired","rest","nap"],"k":[31,14]},"bowling":{"a":"Bowling","b":"1F3B3","j":["sports","fun","play"],"k":[9,13]},"factory":{"a":"Factory","b":"1F3ED","j":["building","industry","pollution","smoke"],"k":[12,10]},"desktop_computer":{"a":"Desktop Computer","b":"1F5A5-FE0F","c":"1F5A5","j":["technology","computing","screen"],"k":[29,51],"o":7},"elephant":{"a":"Elephant","b":"1F418","j":["animal","nature","nose","th","circus"],"k":[13,2]},"rhinoceros":{"a":"Rhinoceros","b":"1F98F","j":["animal","nature","horn"],"k":[42,39],"o":9},"arrow_up_down":{"a":"Up Down Arrow","b":"2195-FE0F","c":"2195","j":["blue-square","direction","way","vertical"],"k":[46,34],"o":1},"cricket_bat_and_ball":{"a":"Cricket Bat and Ball","b":"1F3CF","k":[11,32],"o":8},"printer":{"a":"Printer","b":"1F5A8-FE0F","c":"1F5A8","j":["paper","ink"],"k":[30,0],"o":7},"poultry_leg":{"a":"Poultry Leg","b":"1F357","j":["food","meat","drumstick","bird","chicken","turkey"],"k":[7,25]},"tired_face":{"a":"Tired Face","b":"1F62B","j":["sick","whine","upset","frustrated"],"k":[31,15]},"japanese_castle":{"a":"Japanese Castle","b":"1F3EF","j":["photo","building"],"k":[12,12]},"flag-bn":{"a":"Brunei Flag","b":"1F1E7-1F1F3","k":[1,7]},"field_hockey_stick_and_ball":{"a":"Field Hockey Stick and Ball","b":"1F3D1","k":[11,34],"o":8},"sleeping":{"a":"Sleeping Face","b":"1F634","j":["face","tired","sleepy","night","zzz"],"k":[31,24]},"left_right_arrow":{"a":"Left Right Arrow","b":"2194-FE0F","c":"2194","j":["shape","direction","horizontal","sideways"],"k":[46,33],"o":1},"keyboard":{"a":"Keyboard","b":"2328-FE0F","c":"2328","j":["technology","computer","type","input","text"],"k":[46,43],"o":1},"european_castle":{"a":"European Castle","b":"1F3F0","j":["building","royalty","history"],"k":[12,13]},"mouse":{"a":"Mouse Face","b":"1F42D","j":["animal","nature","cheese_wedge","rodent"],"k":[13,23]},"flag-bo":{"a":"Bolivia Flag","b":"1F1E7-1F1F4","k":[1,8]},"cut_of_meat":{"a":"Cut of Meat","b":"1F969","k":[42,21],"o":10},"ice_hockey_stick_and_puck":{"a":"Ice Hockey Stick and Puck","b":"1F3D2","k":[11,35],"o":8},"mouse2":{"a":"Mouse","b":"1F401","j":["animal","nature","rodent"],"k":[12,31]},"three_button_mouse":{"a":"Three Button Mouse","b":"1F5B1-FE0F","c":"1F5B1","k":[30,1],"o":7},"leftwards_arrow_with_hook":{"a":"Leftwards Arrow with Hook","b":"21A9-FE0F","c":"21A9","j":["back","return","blue-square","undo","enter"],"k":[46,39],"o":1},"bacon":{"a":"Bacon","b":"1F953","j":["food","breakfast","pork","pig","meat"],"k":[41,51],"o":9},"relieved":{"a":"Relieved Face","b":"1F60C","j":["face","relaxed","phew","massage","happiness"],"k":[30,36]},"wedding":{"a":"Wedding","b":"1F492","j":["love","like","affection","couple","marriage","bride","groom"],"k":[24,44]},"tokyo_tower":{"a":"Tokyo Tower","b":"1F5FC","j":["photo","japanese"],"k":[30,20]},"arrow_right_hook":{"a":"Rightwards Arrow with Hook","b":"21AA-FE0F","c":"21AA","j":["blue-square","return","rotate","direction"],"k":[46,40],"o":1},"hamburger":{"a":"Hamburger","b":"1F354","j":["meat","fast food","beef","cheeseburger","mcdonalds","burger king"],"k":[7,22]},"stuck_out_tongue":{"a":"Face with Stuck-out Tongue","b":"1F61B","j":["face","prank","childish","playful","mischievous","smile","tongue"],"k":[30,51],"l":[":p",":-p",":P",":-P",":b",":-b"],"m":":p"},"trackball":{"a":"Trackball","b":"1F5B2-FE0F","c":"1F5B2","j":["technology","trackpad"],"k":[30,2],"o":7},"flag-br":{"a":"Brazil Flag","b":"1F1E7-1F1F7","k":[1,10]},"rat":{"a":"Rat","b":"1F400","j":["animal","mouse","rodent"],"k":[12,30]},"table_tennis_paddle_and_ball":{"a":"Table Tennis Paddle and Ball","b":"1F3D3","k":[11,36],"o":8},"minidisc":{"a":"Minidisc","b":"1F4BD","j":["technology","record","data","disk","90s"],"k":[25,40]},"stuck_out_tongue_winking_eye":{"a":"Face with Stuck-out Tongue and Winking Eye","b":"1F61C","j":["face","prank","childish","playful","mischievous","smile","wink","tongue"],"k":[31,0],"l":[";p",";-p",";b",";-b",";P",";-P"],"m":";p"},"fries":{"a":"French Fries","b":"1F35F","j":["chips","snack","fast food"],"k":[7,33]},"badminton_racquet_and_shuttlecock":{"a":"Badminton Racquet and Shuttlecock","b":"1F3F8","k":[12,22],"o":8},"statue_of_liberty":{"a":"Statue of Liberty","b":"1F5FD","j":["american","newyork"],"k":[30,21]},"flag-bs":{"a":"Bahamas Flag","b":"1F1E7-1F1F8","k":[1,11]},"arrow_heading_up":{"a":"Arrow Pointing Rightwards Then Curving Upwards","b":"2934-FE0F","c":"2934","j":["blue-square","direction","top"],"k":[50,15],"o":3},"hamster":{"a":"Hamster Face","b":"1F439","j":["animal","nature"],"k":[13,35]},"stuck_out_tongue_closed_eyes":{"a":"Face with Stuck-out Tongue and Tightly-Closed Eyes","b":"1F61D","j":["face","prank","playful","mischievous","smile","tongue"],"k":[31,1]},"pizza":{"a":"Slice of Pizza","b":"1F355","j":["food","party"],"k":[7,23]},"boxing_glove":{"a":"Boxing Glove","b":"1F94A","j":["sports","fighting"],"k":[41,45],"o":9},"floppy_disk":{"a":"Floppy Disk","b":"1F4BE","j":["oldschool","technology","save","90s","80s"],"k":[25,41]},"arrow_heading_down":{"a":"Arrow Pointing Rightwards Then Curving Downwards","b":"2935-FE0F","c":"2935","j":["blue-square","direction","bottom"],"k":[50,16],"o":3},"flag-bt":{"a":"Bhutan Flag","b":"1F1E7-1F1F9","k":[1,12]},"rabbit":{"a":"Rabbit Face","b":"1F430","j":["animal","nature","pet","spring","magic","bunny"],"k":[13,26]},"church":{"a":"Church","b":"26EA","j":["building","religion","christ"],"k":[48,37],"o":5},"drooling_face":{"a":"Drooling Face","b":"1F924","j":["face"],"k":[38,27],"o":9},"flag-bv":{"a":"Bouvet Island Flag","b":"1F1E7-1F1FB","k":[1,13]},"mosque":{"a":"Mosque","b":"1F54C","j":["islam","worship","minaret"],"k":[28,15],"o":8},"rabbit2":{"a":"Rabbit","b":"1F407","j":["animal","nature","pet","magic","spring"],"k":[12,37]},"hotdog":{"a":"Hot Dog","b":"1F32D","j":["food","frankfurter"],"k":[6,35],"o":8},"martial_arts_uniform":{"a":"Martial Arts Uniform","b":"1F94B","j":["judo","karate","taekwondo"],"k":[41,46],"o":9},"arrows_clockwise":{"a":"Clockwise Downwards and Upwards Open Circle Arrows","b":"1F503","j":["sync","cycle","round","repeat"],"k":[27,5]},"cd":{"a":"Optical Disc","b":"1F4BF","j":["technology","dvd","disk","disc","90s"],"k":[25,42]},"arrows_counterclockwise":{"a":"Anticlockwise Downwards and Upwards Open Circle Arrows","b":"1F504","j":["blue-square","sync","cycle"],"k":[27,6]},"sandwich":{"a":"Sandwich","b":"1F96A","k":[42,22],"o":10},"chipmunk":{"a":"Chipmunk","b":"1F43F-FE0F","c":"1F43F","j":["animal","nature","rodent","squirrel"],"k":[13,41],"o":7},"synagogue":{"a":"Synagogue","b":"1F54D","j":["judaism","worship","temple","jewish"],"k":[28,16],"o":8},"unamused":{"a":"Unamused Face","b":"1F612","j":["indifference","bored","straight face","serious","sarcasm"],"k":[30,42],"m":":("},"goal_net":{"a":"Goal Net","b":"1F945","j":["sports"],"k":[41,41],"o":9},"flag-bw":{"a":"Botswana Flag","b":"1F1E7-1F1FC","k":[1,14]},"dvd":{"a":"Dvd","b":"1F4C0","j":["cd","disk","disc"],"k":[25,43]},"hedgehog":{"a":"Hedgehog","b":"1F994","k":[42,44],"o":10},"dart":{"a":"Direct Hit","b":"1F3AF","j":["game","play","bar"],"k":[9,9]},"taco":{"a":"Taco","b":"1F32E","j":["food","mexican"],"k":[6,36],"o":8},"back":{"a":"Back with Leftwards Arrow Above","b":"1F519","j":["arrow","words","return"],"k":[27,27]},"flag-by":{"a":"Belarus Flag","b":"1F1E7-1F1FE","k":[1,15]},"shinto_shrine":{"a":"Shinto Shrine","b":"26E9-FE0F","c":"26E9","j":["temple","japan","kyoto"],"k":[48,36],"o":5},"movie_camera":{"a":"Movie Camera","b":"1F3A5","j":["film","record"],"k":[8,51]},"sweat":{"a":"Face with Cold Sweat","b":"1F613","j":["face","hot","sad","tired","exercise"],"k":[30,43]},"burrito":{"a":"Burrito","b":"1F32F","j":["food","mexican"],"k":[6,37],"o":8},"flag-bz":{"a":"Belize Flag","b":"1F1E7-1F1FF","k":[1,16]},"pensive":{"a":"Pensive Face","b":"1F614","j":["face","sad","depressed","upset"],"k":[30,44]},"kaaba":{"a":"Kaaba","b":"1F54B","j":["mecca","mosque","islam"],"k":[28,14],"o":8},"film_frames":{"a":"Film Frames","b":"1F39E-FE0F","c":"1F39E","k":[8,44],"o":7},"bat":{"a":"Bat","b":"1F987","j":["animal","nature","blind","vampire"],"k":[42,31],"o":9},"golf":{"a":"Flag in Hole","b":"26F3","j":["sports","business","flag","hole","summer"],"k":[48,41],"o":5},"end":{"a":"End with Leftwards Arrow Above","b":"1F51A","j":["words","arrow"],"k":[27,28]},"film_projector":{"a":"Film Projector","b":"1F4FD-FE0F","c":"1F4FD","j":["video","tape","record","movie"],"k":[27,0],"o":7},"bear":{"a":"Bear Face","b":"1F43B","j":["animal","nature","wild"],"k":[13,37]},"ice_skate":{"a":"Ice Skate","b":"26F8-FE0F","c":"26F8","j":["sports"],"k":[48,45],"o":5},"fountain":{"a":"Fountain","b":"26F2","j":["photo","summer","water","fresh"],"k":[48,40],"o":5},"confused":{"a":"Confused Face","b":"1F615","j":["face","indifference","huh","weird","hmmm",":/"],"k":[30,45],"l":[":\\",":-\\",":/",":-/"]},"flag-ca":{"a":"Canada Flag","b":"1F1E8-1F1E6","k":[1,17]},"on":{"a":"On with Exclamation Mark with Left Right Arrow Above","b":"1F51B","j":["arrow","words"],"k":[27,29]},"stuffed_flatbread":{"a":"Stuffed Flatbread","b":"1F959","j":["food","flatbread","stuffed","gyro"],"k":[42,5],"o":9},"soon":{"a":"Soon with Rightwards Arrow Above","b":"1F51C","j":["arrow","words"],"k":[27,30]},"upside_down_face":{"a":"Upside-Down Face","b":"1F643","j":["face","flipped","silly","smile"],"k":[31,39],"o":8},"fishing_pole_and_fish":{"a":"Fishing Pole and Fish","b":"1F3A3","j":["food","hobby","summer"],"k":[8,49]},"tent":{"a":"Tent","b":"26FA","j":["photo","camping","outdoors"],"k":[49,12],"o":5},"clapper":{"a":"Clapper Board","b":"1F3AC","j":["movie","film","record"],"k":[9,6]},"egg":{"a":"Egg","b":"1F95A","j":["food","chicken","breakfast"],"k":[42,6],"o":9},"flag-cc":{"a":"Cocos (keeling) Islands Flag","b":"1F1E8-1F1E8","k":[1,18]},"koala":{"a":"Koala","b":"1F428","j":["animal","nature"],"k":[13,18]},"foggy":{"a":"Foggy","b":"1F301","j":["photo","mountain"],"k":[5,45]},"tv":{"a":"Television","b":"1F4FA","j":["technology","program","oldschool","show","television"],"k":[26,49]},"panda_face":{"a":"Panda Face","b":"1F43C","j":["animal","nature","panda"],"k":[13,38]},"fried_egg":{"a":"Cooking","b":"1F373","j":["food","breakfast","kitchen","egg"],"k":[8,1],"n":["cooking"]},"top":{"a":"Top with Upwards Arrow Above","b":"1F51D","j":["words","blue-square"],"k":[27,31]},"flag-cd":{"a":"Congo - Kinshasa Flag","b":"1F1E8-1F1E9","k":[1,19]},"money_mouth_face":{"a":"Money-Mouth Face","b":"1F911","j":["face","rich","dollar","money"],"k":[37,25],"o":8},"running_shirt_with_sash":{"a":"Running Shirt with Sash","b":"1F3BD","j":["play","pageant"],"k":[9,23]},"astonished":{"a":"Astonished Face","b":"1F632","j":["face","xox","surprised","poisoned"],"k":[31,22]},"feet":{"a":"Paw Prints","b":"1F43E","k":[13,40],"n":["paw_prints"]},"camera":{"a":"Camera","b":"1F4F7","j":["gadgets","photography"],"k":[26,46]},"flag-cf":{"a":"Central African Republic Flag","b":"1F1E8-1F1EB","k":[1,20]},"place_of_worship":{"a":"Place of Worship","b":"1F6D0","j":["religion","church","temple","prayer"],"k":[37,5],"o":8},"night_with_stars":{"a":"Night with Stars","b":"1F303","j":["evening","city","downtown"],"k":[5,47]},"ski":{"a":"Ski and Ski Boot","b":"1F3BF","j":["sports","winter","cold","snow"],"k":[9,25]},"shallow_pan_of_food":{"a":"Shallow Pan of Food","b":"1F958","j":["food","cooking","casserole","paella"],"k":[42,4],"o":9},"camera_with_flash":{"a":"Camera with Flash","b":"1F4F8","k":[26,47],"o":7},"sunrise_over_mountains":{"a":"Sunrise over Mountains","b":"1F304","j":["view","vacation","photo"],"k":[5,48]},"turkey":{"a":"Turkey","b":"1F983","j":["animal","bird"],"k":[42,27],"o":8},"white_frowning_face":{"a":"White Frowning Face","b":"2639-FE0F","c":"2639","k":[47,40],"o":1},"flag-cg":{"a":"Congo - Brazzaville Flag","b":"1F1E8-1F1EC","k":[1,21]},"stew":{"a":"Pot of Food","b":"1F372","j":["food","meat","soup"],"k":[8,0]},"sled":{"a":"Sled","b":"1F6F7","k":[37,22],"o":10},"atom_symbol":{"a":"Atom Symbol","b":"269B-FE0F","c":"269B","j":["science","physics","chemistry"],"k":[48,18],"o":4},"curling_stone":{"a":"Curling Stone","b":"1F94C","k":[41,47],"o":10},"slightly_frowning_face":{"a":"Slightly Frowning Face","b":"1F641","j":["face","frowning","disappointed","sad","upset"],"k":[31,37],"o":7},"sunrise":{"a":"Sunrise","b":"1F305","j":["morning","view","vacation","photo"],"k":[5,49]},"om_symbol":{"a":"Om Symbol","b":"1F549-FE0F","c":"1F549","k":[28,12],"o":7},"chicken":{"a":"Chicken","b":"1F414","j":["animal","cluck","nature","bird"],"k":[12,50]},"bowl_with_spoon":{"a":"Bowl with Spoon","b":"1F963","k":[42,15],"o":10},"flag-ch":{"a":"Switzerland Flag","b":"1F1E8-1F1ED","k":[1,22]},"video_camera":{"a":"Video Camera","b":"1F4F9","j":["film","record"],"k":[26,48]},"video_game":{"a":"Video Game","b":"1F3AE","j":["play","console","PS4","controller"],"k":[9,8]},"rooster":{"a":"Rooster","b":"1F413","j":["animal","nature","chicken"],"k":[12,49]},"vhs":{"a":"Videocassette","b":"1F4FC","j":["record","video","oldschool","90s","80s"],"k":[26,51]},"city_sunset":{"a":"Cityscape at Dusk","b":"1F306","j":["photo","evening","sky","buildings"],"k":[5,50]},"confounded":{"a":"Confounded Face","b":"1F616","j":["face","confused","sick","unwell","oops",":S"],"k":[30,46]},"green_salad":{"a":"Green Salad","b":"1F957","j":["food","healthy","lettuce"],"k":[42,3],"o":9},"star_of_david":{"a":"Star of David","b":"2721-FE0F","c":"2721","j":["judaism"],"k":[49,47],"o":1},"flag-ci":{"a":"Côte D’ivoire Flag","b":"1F1E8-1F1EE","k":[1,23]},"popcorn":{"a":"Popcorn","b":"1F37F","j":["food","movie theater","films","snack"],"k":[8,13],"o":8},"city_sunrise":{"a":"Sunset over Buildings","b":"1F307","j":["photo","good morning","dawn"],"k":[5,51]},"disappointed":{"a":"Disappointed Face","b":"1F61E","j":["face","sad","upset","depressed",":("],"k":[31,2],"l":["):",":(",":-("],"m":":("},"mag":{"a":"Left-Pointing Magnifying Glass","b":"1F50D","j":["search","zoom","find","detective"],"k":[27,15]},"hatching_chick":{"a":"Hatching Chick","b":"1F423","j":["animal","chicken","egg","born","baby","bird"],"k":[13,13]},"joystick":{"a":"Joystick","b":"1F579-FE0F","c":"1F579","j":["game","play"],"k":[29,20],"o":7},"wheel_of_dharma":{"a":"Wheel of Dharma","b":"2638-FE0F","c":"2638","j":["hinduism","buddhism","sikhism","jainism"],"k":[47,39],"o":1},"flag-ck":{"a":"Cook Islands Flag","b":"1F1E8-1F1F0","k":[1,24]},"canned_food":{"a":"Canned Food","b":"1F96B","k":[42,23],"o":10},"worried":{"a":"Worried Face","b":"1F61F","j":["face","concern","nervous",":("],"k":[31,3]},"baby_chick":{"a":"Baby Chick","b":"1F424","j":["animal","chicken","bird"],"k":[13,14]},"flag-cl":{"a":"Chile Flag","b":"1F1E8-1F1F1","k":[1,25]},"game_die":{"a":"Game Die","b":"1F3B2","j":["dice","random","tabletop","play","luck"],"k":[9,12]},"mag_right":{"a":"Right-Pointing Magnifying Glass","b":"1F50E","j":["search","zoom","find","detective"],"k":[27,16]},"yin_yang":{"a":"Yin Yang","b":"262F-FE0F","c":"262F","j":["balance"],"k":[47,38],"o":1},"bridge_at_night":{"a":"Bridge at Night","b":"1F309","j":["photo","sanfrancisco"],"k":[6,1]},"spades":{"a":"Black Spade Suit","b":"2660-FE0F","c":"2660","j":["poker","cards","suits","magic"],"k":[48,4],"o":1},"hatched_chick":{"a":"Front-Facing Baby Chick","b":"1F425","j":["animal","chicken","baby","bird"],"k":[13,15]},"flag-cm":{"a":"Cameroon Flag","b":"1F1E8-1F1F2","k":[1,26]},"latin_cross":{"a":"Latin Cross","b":"271D-FE0F","c":"271D","j":["christianity"],"k":[49,46],"o":1},"triumph":{"a":"Face with Look of Triumph","b":"1F624","j":["face","gas","phew","proud","pride"],"k":[31,8]},"hotsprings":{"a":"Hot Springs","b":"2668-FE0F","c":"2668","j":["bath","warm","relax"],"k":[48,8],"o":1},"bento":{"a":"Bento Box","b":"1F371","j":["food","japanese","box"],"k":[7,51]},"microscope":{"a":"Microscope","b":"1F52C","j":["laboratory","experiment","zoomin","science","study"],"k":[27,46]},"cry":{"a":"Crying Face","b":"1F622","j":["face","tears","sad","depressed","upset",":'("],"k":[31,6],"l":[":'("],"m":":'("},"bird":{"a":"Bird","b":"1F426","j":["animal","nature","fly","tweet","spring"],"k":[13,16]},"cn":{"a":"China Flag","b":"1F1E8-1F1F3","j":["china","chinese","prc","flag","country","nation","banner"],"k":[1,27],"n":["flag-cn"]},"telescope":{"a":"Telescope","b":"1F52D","j":["stars","space","zoom","science","astronomy"],"k":[27,47]},"rice_cracker":{"a":"Rice Cracker","b":"1F358","j":["food","japanese"],"k":[7,26]},"hearts":{"a":"Black Heart Suit","b":"2665-FE0F","c":"2665","j":["poker","cards","magic","suits"],"k":[48,6],"o":1},"orthodox_cross":{"a":"Orthodox Cross","b":"2626-FE0F","c":"2626","j":["suppedaneum","religion"],"k":[47,35],"o":1},"milky_way":{"a":"Milky Way","b":"1F30C","j":["photo","space","stars"],"k":[6,4]},"rice_ball":{"a":"Rice Ball","b":"1F359","j":["food","japanese"],"k":[7,27]},"satellite_antenna":{"a":"Satellite Antenna","b":"1F4E1","k":[26,24]},"flag-co":{"a":"Colombia Flag","b":"1F1E8-1F1F4","k":[1,28]},"carousel_horse":{"a":"Carousel Horse","b":"1F3A0","j":["photo","carnival"],"k":[8,46]},"sob":{"a":"Loudly Crying Face","b":"1F62D","j":["face","cry","tears","sad","upset","depressed"],"k":[31,17],"m":":'("},"diamonds":{"a":"Black Diamond Suit","b":"2666-FE0F","c":"2666","j":["poker","cards","magic","suits"],"k":[48,7],"o":1},"star_and_crescent":{"a":"Star and Crescent","b":"262A-FE0F","c":"262A","j":["islam"],"k":[47,36],"o":1},"penguin":{"a":"Penguin","b":"1F427","j":["animal","nature"],"k":[13,17]},"dove_of_peace":{"a":"Dove of Peace","b":"1F54A-FE0F","c":"1F54A","k":[28,13],"o":7},"flag-cp":{"a":"Clipperton Island Flag","b":"1F1E8-1F1F5","k":[1,29]},"ferris_wheel":{"a":"Ferris Wheel","b":"1F3A1","j":["photo","carnival","londoneye"],"k":[8,47]},"clubs":{"a":"Black Club Suit","b":"2663-FE0F","c":"2663","j":["poker","cards","magic","suits"],"k":[48,5],"o":1},"peace_symbol":{"a":"Peace Symbol","b":"262E-FE0F","c":"262E","j":["hippie"],"k":[47,37],"o":1},"candle":{"a":"Candle","b":"1F56F-FE0F","c":"1F56F","j":["fire","wax"],"k":[28,42],"o":7},"frowning":{"a":"Frowning Face with Open Mouth","b":"1F626","j":["face","aw","what"],"k":[31,10]},"rice":{"a":"Cooked Rice","b":"1F35A","j":["food","china","asian"],"k":[7,28]},"flag-cr":{"a":"Costa Rica Flag","b":"1F1E8-1F1F7","k":[1,30]},"roller_coaster":{"a":"Roller Coaster","b":"1F3A2","j":["carnival","playground","photo","fun"],"k":[8,48]},"menorah_with_nine_branches":{"a":"Menorah with Nine Branches","b":"1F54E","k":[28,17],"o":8},"black_joker":{"a":"Playing Card Black Joker","b":"1F0CF","j":["poker","cards","game","play","magic"],"k":[0,15]},"eagle":{"a":"Eagle","b":"1F985","j":["animal","nature","bird"],"k":[42,29],"o":9},"curry":{"a":"Curry and Rice","b":"1F35B","j":["food","spicy","hot","indian"],"k":[7,29]},"bulb":{"a":"Electric Light Bulb","b":"1F4A1","j":["light","electricity","idea"],"k":[25,7]},"anguished":{"a":"Anguished Face","b":"1F627","j":["face","stunned","nervous"],"k":[31,11],"l":["D:"]},"flag-cu":{"a":"Cuba Flag","b":"1F1E8-1F1FA","k":[1,31]},"barber":{"a":"Barber Pole","b":"1F488","j":["hair","salon","style"],"k":[24,34]},"duck":{"a":"Duck","b":"1F986","j":["animal","nature","bird","mallard"],"k":[42,30],"o":9},"six_pointed_star":{"a":"Six Pointed Star with Middle Dot","b":"1F52F","j":["purple-square","religion","jewish","hexagram"],"k":[27,49]},"ramen":{"a":"Steaming Bowl","b":"1F35C","j":["food","japanese","noodle","chopsticks"],"k":[7,30]},"flashlight":{"a":"Electric Torch","b":"1F526","j":["dark","camping","sight","night"],"k":[27,40]},"mahjong":{"a":"Mahjong Tile Red Dragon","b":"1F004","j":["game","play","chinese","kanji"],"k":[0,14],"o":5},"fearful":{"a":"Fearful Face","b":"1F628","j":["face","scared","terrified","nervous","oops","huh"],"k":[31,12]},"aries":{"a":"Aries","b":"2648","j":["sign","purple-square","zodiac","astrology"],"k":[47,44],"o":1},"spaghetti":{"a":"Spaghetti","b":"1F35D","j":["food","italian","noodle"],"k":[7,31]},"circus_tent":{"a":"Circus Tent","b":"1F3AA","j":["festival","carnival","party"],"k":[9,4]},"izakaya_lantern":{"a":"Izakaya Lantern","b":"1F3EE","j":["light","paper","halloween","spooky"],"k":[12,11],"n":["lantern"]},"flag-cv":{"a":"Cape Verde Flag","b":"1F1E8-1F1FB","k":[1,32]},"weary":{"a":"Weary Face","b":"1F629","j":["face","tired","sleepy","sad","frustrated","upset"],"k":[31,13]},"flower_playing_cards":{"a":"Flower Playing Cards","b":"1F3B4","j":["game","sunset","red"],"k":[9,14]},"owl":{"a":"Owl","b":"1F989","j":["animal","nature","bird","hoot"],"k":[42,33],"o":9},"performing_arts":{"a":"Performing Arts","b":"1F3AD","j":["acting","theater","drama"],"k":[9,7]},"frog":{"a":"Frog Face","b":"1F438","j":["animal","nature","croak","toad"],"k":[13,34]},"flag-cw":{"a":"Curaçao Flag","b":"1F1E8-1F1FC","k":[1,33]},"notebook_with_decorative_cover":{"a":"Notebook with Decorative Cover","b":"1F4D4","j":["classroom","notes","record","paper","study"],"k":[26,11]},"exploding_head":{"a":"Shocked Face with Exploding Head","b":"1F92F","k":[39,3],"n":["shocked_face_with_exploding_head"],"o":10},"taurus":{"a":"Taurus","b":"2649","j":["purple-square","sign","zodiac","astrology"],"k":[47,45],"o":1},"sweet_potato":{"a":"Roasted Sweet Potato","b":"1F360","j":["food","nature"],"k":[7,34]},"closed_book":{"a":"Closed Book","b":"1F4D5","j":["read","library","knowledge","textbook","learn"],"k":[26,12]},"gemini":{"a":"Gemini","b":"264A","j":["sign","zodiac","purple-square","astrology"],"k":[47,46],"o":1},"frame_with_picture":{"a":"Frame with Picture","b":"1F5BC-FE0F","c":"1F5BC","k":[30,3],"o":7},"flag-cx":{"a":"Christmas Island Flag","b":"1F1E8-1F1FD","k":[1,34]},"grimacing":{"a":"Grimacing Face","b":"1F62C","j":["face","grimace","teeth"],"k":[31,16]},"crocodile":{"a":"Crocodile","b":"1F40A","j":["animal","nature","reptile","lizard","alligator"],"k":[12,40]},"oden":{"a":"Oden","b":"1F362","j":["food","japanese"],"k":[7,36]},"flag-cy":{"a":"Cyprus Flag","b":"1F1E8-1F1FE","k":[1,35]},"book":{"a":"Open Book","b":"1F4D6","k":[26,13],"n":["open_book"]},"turtle":{"a":"Turtle","b":"1F422","j":["animal","slow","nature","tortoise"],"k":[13,12]},"art":{"a":"Artist Palette","b":"1F3A8","j":["design","paint","draw","colors"],"k":[9,2]},"sushi":{"a":"Sushi","b":"1F363","j":["food","fish","japanese","rice"],"k":[7,37]},"cold_sweat":{"a":"Face with Open Mouth and Cold Sweat","b":"1F630","j":["face","nervous","sweat"],"k":[31,20]},"cancer":{"a":"Cancer","b":"264B","j":["sign","zodiac","purple-square","astrology"],"k":[47,47],"o":1},"fried_shrimp":{"a":"Fried Shrimp","b":"1F364","j":["food","animal","appetizer","summer"],"k":[7,38]},"slot_machine":{"a":"Slot Machine","b":"1F3B0","j":["bet","gamble","vegas","fruit machine","luck","casino"],"k":[9,10]},"scream":{"a":"Face Screaming in Fear","b":"1F631","j":["face","munch","scared","omg"],"k":[31,21]},"green_book":{"a":"Green Book","b":"1F4D7","j":["read","library","knowledge","study"],"k":[26,14]},"leo":{"a":"Leo","b":"264C","j":["sign","purple-square","zodiac","astrology"],"k":[47,48],"o":1},"flag-cz":{"a":"Czechia Flag","b":"1F1E8-1F1FF","k":[1,36]},"lizard":{"a":"Lizard","b":"1F98E","j":["animal","nature","reptile"],"k":[42,38],"o":9},"virgo":{"a":"Virgo","b":"264D","j":["sign","zodiac","purple-square","astrology"],"k":[47,49],"o":1},"steam_locomotive":{"a":"Steam Locomotive","b":"1F682","j":["transportation","vehicle","train"],"k":[34,10]},"de":{"a":"Germany Flag","b":"1F1E9-1F1EA","j":["german","nation","flag","country","banner"],"k":[1,37],"n":["flag-de"]},"flushed":{"a":"Flushed Face","b":"1F633","j":["face","blush","shy","flattered"],"k":[31,23]},"blue_book":{"a":"Blue Book","b":"1F4D8","j":["read","library","knowledge","learn","study"],"k":[26,15]},"snake":{"a":"Snake","b":"1F40D","j":["animal","evil","nature","hiss","python"],"k":[12,43]},"fish_cake":{"a":"Fish Cake with Swirl Design","b":"1F365","j":["food","japan","sea","beach","narutomaki","pink","swirl","kamaboko","surimi","ramen"],"k":[7,39]},"railway_car":{"a":"Railway Car","b":"1F683","j":["transportation","vehicle"],"k":[34,11]},"dango":{"a":"Dango","b":"1F361","j":["food","dessert","sweet","japanese","barbecue","meat"],"k":[7,35]},"orange_book":{"a":"Orange Book","b":"1F4D9","j":["read","library","knowledge","textbook","study"],"k":[26,16]},"libra":{"a":"Libra","b":"264E","j":["sign","purple-square","zodiac","astrology"],"k":[47,50],"o":1},"dragon_face":{"a":"Dragon Face","b":"1F432","j":["animal","myth","nature","chinese","green"],"k":[13,28]},"zany_face":{"a":"Grinning Face with One Large and One Small Eye","b":"1F92A","k":[38,50],"n":["grinning_face_with_one_large_and_one_small_eye"],"o":10},"books":{"a":"Books","b":"1F4DA","j":["literature","library","study"],"k":[26,17]},"dragon":{"a":"Dragon","b":"1F409","j":["animal","myth","nature","chinese","green"],"k":[12,39]},"flag-dj":{"a":"Djibouti Flag","b":"1F1E9-1F1EF","k":[1,39]},"dumpling":{"a":"Dumpling","b":"1F95F","k":[42,11],"o":10},"dizzy_face":{"a":"Dizzy Face","b":"1F635","j":["spent","unconscious","xox","dizzy"],"k":[31,25]},"scorpius":{"a":"Scorpius","b":"264F","j":["sign","zodiac","purple-square","astrology","scorpio"],"k":[47,51],"o":1},"bullettrain_side":{"a":"High-Speed Train","b":"1F684","j":["transportation","vehicle"],"k":[34,12]},"bullettrain_front":{"a":"High-Speed Train with Bullet Nose","b":"1F685","j":["transportation","vehicle","speed","fast","public","travel"],"k":[34,13]},"notebook":{"a":"Notebook","b":"1F4D3","j":["stationery","record","notes","paper","study"],"k":[26,10]},"fortune_cookie":{"a":"Fortune Cookie","b":"1F960","k":[42,12],"o":10},"sagittarius":{"a":"Sagittarius","b":"2650","j":["sign","zodiac","purple-square","astrology"],"k":[48,0],"o":1},"sauropod":{"a":"Sauropod","b":"1F995","k":[42,45],"o":10},"flag-dk":{"a":"Denmark Flag","b":"1F1E9-1F1F0","k":[1,40]},"rage":{"a":"Pouting Face","b":"1F621","j":["angry","mad","hate","despise"],"k":[31,5]},"ledger":{"a":"Ledger","b":"1F4D2","j":["notes","paper"],"k":[26,9]},"angry":{"a":"Angry Face","b":"1F620","j":["mad","face","annoyed","frustrated"],"k":[31,4],"l":[">:(",">:-("]},"t-rex":{"a":"T-Rex","b":"1F996","k":[42,46],"o":10},"capricorn":{"a":"Capricorn","b":"2651","j":["sign","zodiac","purple-square","astrology"],"k":[48,1],"o":1},"takeout_box":{"a":"Takeout Box","b":"1F961","k":[42,13],"o":10},"flag-dm":{"a":"Dominica Flag","b":"1F1E9-1F1F2","k":[1,41]},"train2":{"a":"Train","b":"1F686","j":["transportation","vehicle"],"k":[34,14]},"page_with_curl":{"a":"Page with Curl","b":"1F4C3","j":["documents","office","paper"],"k":[25,46]},"whale":{"a":"Spouting Whale","b":"1F433","j":["animal","nature","sea","ocean"],"k":[13,29]},"face_with_symbols_on_mouth":{"a":"Serious Face with Symbols Covering Mouth","b":"1F92C","k":[39,0],"n":["serious_face_with_symbols_covering_mouth"],"o":10},"flag-do":{"a":"Dominican Republic Flag","b":"1F1E9-1F1F4","k":[1,42]},"metro":{"a":"Metro","b":"1F687","j":["transportation","blue-square","mrt","underground","tube"],"k":[34,15]},"icecream":{"a":"Soft Ice Cream","b":"1F366","j":["food","hot","dessert","summer"],"k":[7,40]},"aquarius":{"a":"Aquarius","b":"2652","j":["sign","purple-square","zodiac","astrology"],"k":[48,2],"o":1},"flag-dz":{"a":"Algeria Flag","b":"1F1E9-1F1FF","k":[1,43]},"whale2":{"a":"Whale","b":"1F40B","j":["animal","nature","sea","ocean"],"k":[12,41]},"mask":{"a":"Face with Medical Mask","b":"1F637","j":["face","sick","ill","disease"],"k":[31,27]},"scroll":{"a":"Scroll","b":"1F4DC","j":["documents","ancient","history","paper"],"k":[26,19]},"shaved_ice":{"a":"Shaved Ice","b":"1F367","j":["hot","dessert","summer"],"k":[7,41]},"pisces":{"a":"Pisces","b":"2653","j":["purple-square","sign","zodiac","astrology"],"k":[48,3],"o":1},"light_rail":{"a":"Light Rail","b":"1F688","j":["transportation","vehicle"],"k":[34,16]},"dolphin":{"a":"Dolphin","b":"1F42C","j":["animal","nature","fish","sea","ocean","flipper","fins","beach"],"k":[13,22],"n":["flipper"]},"face_with_thermometer":{"a":"Face with Thermometer","b":"1F912","j":["sick","temperature","thermometer","cold","fever"],"k":[37,26],"o":8},"ophiuchus":{"a":"Ophiuchus","b":"26CE","j":["sign","purple-square","constellation","astrology"],"k":[48,31]},"station":{"a":"Station","b":"1F689","j":["transportation","vehicle","public"],"k":[34,17]},"ice_cream":{"a":"Ice Cream","b":"1F368","j":["food","hot","dessert"],"k":[7,42]},"page_facing_up":{"a":"Page Facing Up","b":"1F4C4","j":["documents","office","paper","information"],"k":[25,47]},"doughnut":{"a":"Doughnut","b":"1F369","j":["food","dessert","snack","sweet","donut"],"k":[7,43]},"face_with_head_bandage":{"a":"Face with Head-Bandage","b":"1F915","j":["injured","clumsy","bandage","hurt"],"k":[37,29],"o":8},"fish":{"a":"Fish","b":"1F41F","j":["animal","food","nature"],"k":[13,9]},"newspaper":{"a":"Newspaper","b":"1F4F0","j":["press","headline"],"k":[26,39]},"tram":{"a":"Tram","b":"1F68A","j":["transportation","vehicle"],"k":[34,18]},"flag-ec":{"a":"Ecuador Flag","b":"1F1EA-1F1E8","k":[1,45]},"twisted_rightwards_arrows":{"a":"Twisted Rightwards Arrows","b":"1F500","j":["blue-square","shuffle","music","random"],"k":[27,2]},"flag-ee":{"a":"Estonia Flag","b":"1F1EA-1F1EA","k":[1,46]},"cookie":{"a":"Cookie","b":"1F36A","j":["food","snack","oreo","chocolate","sweet","dessert"],"k":[7,44]},"monorail":{"a":"Monorail","b":"1F69D","j":["transportation","vehicle"],"k":[34,37]},"tropical_fish":{"a":"Tropical Fish","b":"1F420","j":["animal","swim","ocean","beach","nemo"],"k":[13,10]},"rolled_up_newspaper":{"a":"Rolled Up Newspaper","b":"1F5DE-FE0F","c":"1F5DE","k":[30,12],"o":7},"nauseated_face":{"a":"Nauseated Face","b":"1F922","j":["face","vomit","gross","green","sick","throw up","ill"],"k":[38,25],"o":9},"repeat":{"a":"Clockwise Rightwards and Leftwards Open Circle Arrows","b":"1F501","j":["loop","record"],"k":[27,3]},"bookmark_tabs":{"a":"Bookmark Tabs","b":"1F4D1","j":["favorite","save","order","tidy"],"k":[26,8]},"repeat_one":{"a":"Clockwise Rightwards and Leftwards Open Circle Arrows with Circled One Overlay","b":"1F502","j":["blue-square","loop"],"k":[27,4]},"flag-eg":{"a":"Egypt Flag","b":"1F1EA-1F1EC","k":[1,47]},"mountain_railway":{"a":"Mountain Railway","b":"1F69E","j":["transportation","vehicle"],"k":[34,38]},"birthday":{"a":"Birthday Cake","b":"1F382","j":["food","dessert","cake"],"k":[8,16]},"blowfish":{"a":"Blowfish","b":"1F421","j":["animal","nature","food","sea","ocean"],"k":[13,11]},"face_vomiting":{"a":"Face with Open Mouth Vomiting","b":"1F92E","k":[39,2],"n":["face_with_open_mouth_vomiting"],"o":10},"arrow_forward":{"a":"Black Right-Pointing Triangle","b":"25B6-FE0F","c":"25B6","j":["blue-square","right","direction","play"],"k":[47,10],"o":1},"bookmark":{"a":"Bookmark","b":"1F516","j":["favorite","label","save"],"k":[27,24]},"shark":{"a":"Shark","b":"1F988","j":["animal","nature","fish","sea","ocean","jaws","fins","beach"],"k":[42,32],"o":9},"train":{"a":"Tram Car","b":"1F68B","j":["transportation","vehicle","carriage","public","travel"],"k":[34,19]},"sneezing_face":{"a":"Sneezing Face","b":"1F927","j":["face","gesundheit","sneeze","sick","allergy"],"k":[38,47],"o":9},"cake":{"a":"Shortcake","b":"1F370","j":["food","dessert"],"k":[7,50]},"bus":{"a":"Bus","b":"1F68C","j":["car","vehicle","transportation"],"k":[34,20]},"pie":{"a":"Pie","b":"1F967","k":[42,19],"o":10},"innocent":{"a":"Smiling Face with Halo","b":"1F607","j":["face","angel","heaven","halo"],"k":[30,31]},"fast_forward":{"a":"Black Right-Pointing Double Triangle","b":"23E9","j":["blue-square","play","speed","continue"],"k":[46,45]},"label":{"a":"Label","b":"1F3F7-FE0F","c":"1F3F7","j":["sale","tag"],"k":[12,21],"o":7},"octopus":{"a":"Octopus","b":"1F419","j":["animal","creature","ocean","sea","nature","beach"],"k":[13,3]},"flag-er":{"a":"Eritrea Flag","b":"1F1EA-1F1F7","k":[1,49]},"black_right_pointing_double_triangle_with_vertical_bar":{"a":"Black Right Pointing Double Triangle with Vertical Bar","b":"23ED-FE0F","c":"23ED","k":[46,49]},"chocolate_bar":{"a":"Chocolate Bar","b":"1F36B","j":["food","snack","dessert","sweet"],"k":[7,45]},"oncoming_bus":{"a":"Oncoming Bus","b":"1F68D","j":["vehicle","transportation"],"k":[34,21]},"shell":{"a":"Spiral Shell","b":"1F41A","j":["nature","sea","beach"],"k":[13,4]},"face_with_cowboy_hat":{"a":"Face with Cowboy Hat","b":"1F920","k":[38,23],"o":9},"moneybag":{"a":"Money Bag","b":"1F4B0","j":["dollar","payment","coins","sale"],"k":[25,27]},"es":{"a":"Spain Flag","b":"1F1EA-1F1F8","j":["spain","flag","nation","country","banner"],"k":[1,50],"n":["flag-es"]},"crab":{"a":"Crab","b":"1F980","j":["animal","crustacean"],"k":[42,24],"o":8},"yen":{"a":"Banknote with Yen Sign","b":"1F4B4","j":["money","sales","japanese","dollar","currency"],"k":[25,31]},"flag-et":{"a":"Ethiopia Flag","b":"1F1EA-1F1F9","k":[1,51]},"clown_face":{"a":"Clown Face","b":"1F921","j":["face"],"k":[38,24],"o":9},"black_right_pointing_triangle_with_double_vertical_bar":{"a":"Black Right Pointing Triangle with Double Vertical Bar","b":"23EF-FE0F","c":"23EF","k":[46,51]},"trolleybus":{"a":"Trolleybus","b":"1F68E","j":["bart","transportation","vehicle"],"k":[34,22]},"candy":{"a":"Candy","b":"1F36C","j":["snack","dessert","sweet","lolly"],"k":[7,46]},"lying_face":{"a":"Lying Face","b":"1F925","j":["face","lie","pinocchio"],"k":[38,28],"o":9},"arrow_backward":{"a":"Black Left-Pointing Triangle","b":"25C0-FE0F","c":"25C0","j":["blue-square","left","direction"],"k":[47,11],"o":1},"dollar":{"a":"Banknote with Dollar Sign","b":"1F4B5","j":["money","sales","bill","currency"],"k":[25,32]},"shrimp":{"a":"Shrimp","b":"1F990","j":["animal","ocean","nature","seafood"],"k":[42,40],"o":9},"minibus":{"a":"Minibus","b":"1F690","j":["vehicle","car","transportation"],"k":[34,24]},"flag-eu":{"a":"European Union Flag","b":"1F1EA-1F1FA","k":[2,0]},"lollipop":{"a":"Lollipop","b":"1F36D","j":["food","snack","candy","sweet"],"k":[7,47]},"squid":{"a":"Squid","b":"1F991","j":["animal","nature","ocean","sea"],"k":[42,41],"o":9},"euro":{"a":"Banknote with Euro Sign","b":"1F4B6","j":["money","sales","dollar","currency"],"k":[25,33]},"flag-fi":{"a":"Finland Flag","b":"1F1EB-1F1EE","k":[2,1]},"ambulance":{"a":"Ambulance","b":"1F691","j":["health","911","hospital"],"k":[34,25]},"custard":{"a":"Custard","b":"1F36E","j":["dessert","food"],"k":[7,48]},"shushing_face":{"a":"Face with Finger Covering Closed Lips","b":"1F92B","k":[38,51],"n":["face_with_finger_covering_closed_lips"],"o":10},"rewind":{"a":"Black Left-Pointing Double Triangle","b":"23EA","j":["play","blue-square"],"k":[46,46]},"black_left_pointing_double_triangle_with_vertical_bar":{"a":"Black Left Pointing Double Triangle with Vertical Bar","b":"23EE-FE0F","c":"23EE","k":[46,50]},"face_with_hand_over_mouth":{"a":"Smiling Face with Smiling Eyes and Hand Covering Mouth","b":"1F92D","k":[39,1],"n":["smiling_face_with_smiling_eyes_and_hand_covering_mouth"],"o":10},"flag-fj":{"a":"Fiji Flag","b":"1F1EB-1F1EF","k":[2,2]},"honey_pot":{"a":"Honey Pot","b":"1F36F","j":["bees","sweet","kitchen"],"k":[7,49]},"snail":{"a":"Snail","b":"1F40C","j":["slow","animal","shell"],"k":[12,42]},"pound":{"a":"Banknote with Pound Sign","b":"1F4B7","j":["british","sterling","money","sales","bills","uk","england","currency"],"k":[25,34]},"fire_engine":{"a":"Fire Engine","b":"1F692","j":["transportation","cars","vehicle"],"k":[34,26]},"baby_bottle":{"a":"Baby Bottle","b":"1F37C","j":["food","container","milk"],"k":[8,10]},"butterfly":{"a":"Butterfly","b":"1F98B","j":["animal","insect","nature","caterpillar"],"k":[42,35],"o":9},"money_with_wings":{"a":"Money with Wings","b":"1F4B8","j":["dollar","bills","payment","sale"],"k":[25,35]},"face_with_monocle":{"a":"Face with Monocle","b":"1F9D0","k":[42,49],"o":10},"police_car":{"a":"Police Car","b":"1F693","j":["vehicle","cars","transportation","law","legal","enforcement"],"k":[34,27]},"arrow_up_small":{"a":"Up-Pointing Small Red Triangle","b":"1F53C","j":["blue-square","triangle","direction","point","forward","top"],"k":[28,10]},"flag-fm":{"a":"Micronesia Flag","b":"1F1EB-1F1F2","k":[2,4]},"glass_of_milk":{"a":"Glass of Milk","b":"1F95B","k":[42,7],"o":9},"credit_card":{"a":"Credit Card","b":"1F4B3","j":["money","sales","dollar","bill","payment","shopping"],"k":[25,30]},"oncoming_police_car":{"a":"Oncoming Police Car","b":"1F694","j":["vehicle","law","legal","enforcement","911"],"k":[34,28]},"bug":{"a":"Bug","b":"1F41B","j":["animal","insect","nature","worm"],"k":[13,5]},"nerd_face":{"a":"Nerd Face","b":"1F913","j":["face","nerdy","geek","dork"],"k":[37,27],"o":8},"arrow_double_up":{"a":"Black Up-Pointing Double Triangle","b":"23EB","j":["blue-square","direction","top"],"k":[46,47]},"chart":{"a":"Chart with Upwards Trend and Yen Sign","b":"1F4B9","j":["green-square","graph","presentation","stats"],"k":[25,36]},"flag-fo":{"a":"Faroe Islands Flag","b":"1F1EB-1F1F4","k":[2,5]},"ant":{"a":"Ant","b":"1F41C","j":["animal","insect","nature","bug"],"k":[13,6]},"arrow_down_small":{"a":"Down-Pointing Small Red Triangle","b":"1F53D","j":["blue-square","direction","bottom"],"k":[28,11]},"smiling_imp":{"a":"Smiling Face with Horns","b":"1F608","j":["devil","horns"],"k":[30,32]},"taxi":{"a":"Taxi","b":"1F695","j":["uber","vehicle","cars","transportation"],"k":[34,29]},"coffee":{"a":"Hot Beverage","b":"2615","j":["beverage","caffeine","latte","espresso"],"k":[47,24],"o":4},"fr":{"a":"France Flag","b":"1F1EB-1F1F7","j":["banner","flag","nation","france","french","country"],"k":[2,6],"n":["flag-fr"]},"oncoming_taxi":{"a":"Oncoming Taxi","b":"1F696","j":["vehicle","cars","uber"],"k":[34,30]},"arrow_double_down":{"a":"Black Down-Pointing Double Triangle","b":"23EC","j":["blue-square","direction","bottom"],"k":[46,48]},"imp":{"a":"Imp","b":"1F47F","j":["devil","angry","horns"],"k":[22,51]},"currency_exchange":{"a":"Currency Exchange","b":"1F4B1","j":["money","sales","dollar","travel"],"k":[25,28]},"tea":{"a":"Teacup Without Handle","b":"1F375","j":["drink","bowl","breakfast","green","british"],"k":[8,3]},"bee":{"a":"Honeybee","b":"1F41D","k":[13,7],"n":["honeybee"]},"heavy_dollar_sign":{"a":"Heavy Dollar Sign","b":"1F4B2","j":["money","sales","payment","currency","buck"],"k":[25,29]},"car":{"a":"Automobile","b":"1F697","k":[34,31],"n":["red_car"]},"sake":{"a":"Sake Bottle and Cup","b":"1F376","j":["wine","drink","drunk","beverage","japanese","alcohol","booze"],"k":[8,4]},"flag-ga":{"a":"Gabon Flag","b":"1F1EC-1F1E6","k":[2,7]},"beetle":{"a":"Lady Beetle","b":"1F41E","j":["animal","insect","nature","ladybug"],"k":[13,8]},"japanese_ogre":{"a":"Japanese Ogre","b":"1F479","j":["monster","red","mask","halloween","scary","creepy","devil","demon","japanese","ogre"],"k":[22,40]},"double_vertical_bar":{"a":"Double Vertical Bar","b":"23F8-FE0F","c":"23F8","k":[47,4],"o":7},"champagne":{"a":"Bottle with Popping Cork","b":"1F37E","j":["drink","wine","bottle","celebration"],"k":[8,12],"o":8},"japanese_goblin":{"a":"Japanese Goblin","b":"1F47A","j":["red","evil","mask","monster","scary","creepy","japanese","goblin"],"k":[22,41]},"black_square_for_stop":{"a":"Black Square for Stop","b":"23F9-FE0F","c":"23F9","k":[47,5],"o":7},"oncoming_automobile":{"a":"Oncoming Automobile","b":"1F698","j":["car","vehicle","transportation"],"k":[34,32]},"email":{"a":"Envelope","b":"2709-FE0F","c":"2709","j":["letter","postal","inbox","communication"],"k":[49,17],"n":["envelope"],"o":1},"cricket":{"a":"Cricket","b":"1F997","j":["sports"],"k":[42,47],"o":10},"gb":{"a":"United Kingdom Flag","b":"1F1EC-1F1E7","k":[2,8],"n":["uk","flag-gb"]},"black_circle_for_record":{"a":"Black Circle for Record","b":"23FA-FE0F","c":"23FA","k":[47,6],"o":7},"flag-gd":{"a":"Grenada Flag","b":"1F1EC-1F1E9","k":[2,9]},"spider":{"a":"Spider","b":"1F577-FE0F","c":"1F577","j":["animal","arachnid"],"k":[29,18],"o":7},"blue_car":{"a":"Recreational Vehicle","b":"1F699","j":["transportation","vehicle"],"k":[34,33]},"skull":{"a":"Skull","b":"1F480","j":["dead","skeleton","creepy","death"],"k":[23,0]},"e-mail":{"a":"E-Mail Symbol","b":"1F4E7","j":["communication","inbox"],"k":[26,30]},"wine_glass":{"a":"Wine Glass","b":"1F377","j":["drink","beverage","drunk","alcohol","booze"],"k":[8,5]},"spider_web":{"a":"Spider Web","b":"1F578-FE0F","c":"1F578","j":["animal","insect","arachnid","silk"],"k":[29,19],"o":7},"cocktail":{"a":"Cocktail Glass","b":"1F378","j":["drink","drunk","alcohol","beverage","booze","mojito"],"k":[8,6]},"skull_and_crossbones":{"a":"Skull and Crossbones","b":"2620-FE0F","c":"2620","j":["poison","danger","deadly","scary","death","pirate","evil"],"k":[47,32],"o":1},"flag-ge":{"a":"Georgia Flag","b":"1F1EC-1F1EA","k":[2,10]},"eject":{"a":"Eject","b":"23CF-FE0F","c":"23CF","k":[46,44],"o":4},"truck":{"a":"Delivery Truck","b":"1F69A","j":["cars","transportation"],"k":[34,34]},"incoming_envelope":{"a":"Incoming Envelope","b":"1F4E8","j":["email","inbox"],"k":[26,31]},"tropical_drink":{"a":"Tropical Drink","b":"1F379","j":["beverage","cocktail","summer","beach","alcohol","booze","mojito"],"k":[8,7]},"scorpion":{"a":"Scorpion","b":"1F982","j":["animal","arachnid"],"k":[42,26],"o":8},"cinema":{"a":"Cinema","b":"1F3A6","j":["blue-square","record","film","movie","curtain","stage","theater"],"k":[9,0]},"articulated_lorry":{"a":"Articulated Lorry","b":"1F69B","j":["vehicle","cars","transportation","express"],"k":[34,35]},"envelope_with_arrow":{"a":"Envelope with Downwards Arrow Above","b":"1F4E9","j":["email","communication"],"k":[26,32]},"ghost":{"a":"Ghost","b":"1F47B","j":["halloween","spooky","scary"],"k":[22,42]},"bouquet":{"a":"Bouquet","b":"1F490","j":["flowers","nature","spring"],"k":[24,42]},"tractor":{"a":"Tractor","b":"1F69C","j":["vehicle","car","farming","agriculture"],"k":[34,36]},"beer":{"a":"Beer Mug","b":"1F37A","j":["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],"k":[8,8]},"outbox_tray":{"a":"Outbox Tray","b":"1F4E4","j":["inbox","email"],"k":[26,27]},"low_brightness":{"a":"Low Brightness Symbol","b":"1F505","j":["sun","afternoon","warm","summer"],"k":[27,7]},"alien":{"a":"Extraterrestrial Alien","b":"1F47D","j":["UFO","paul","weird","outer_space"],"k":[22,49]},"flag-gg":{"a":"Guernsey Flag","b":"1F1EC-1F1EC","k":[2,12]},"cherry_blossom":{"a":"Cherry Blossom","b":"1F338","j":["nature","plant","spring","flower"],"k":[6,46]},"inbox_tray":{"a":"Inbox Tray","b":"1F4E5","j":["email","documents"],"k":[26,28]},"flag-gh":{"a":"Ghana Flag","b":"1F1EC-1F1ED","k":[2,13]},"bike":{"a":"Bicycle","b":"1F6B2","j":["sports","bicycle","exercise","hipster"],"k":[35,23]},"space_invader":{"a":"Alien Monster","b":"1F47E","j":["game","arcade","play"],"k":[22,50]},"beers":{"a":"Clinking Beer Mugs","b":"1F37B","j":["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],"k":[8,9]},"high_brightness":{"a":"High Brightness Symbol","b":"1F506","j":["sun","light"],"k":[27,8]},"package":{"a":"Package","b":"1F4E6","j":["mail","gift","cardboard","box","moving"],"k":[26,29]},"scooter":{"a":"Scooter","b":"1F6F4","k":[37,19],"o":9},"white_flower":{"a":"White Flower","b":"1F4AE","j":["japanese","spring"],"k":[25,25]},"clinking_glasses":{"a":"Clinking Glasses","b":"1F942","j":["beverage","drink","party","alcohol","celebrate","cheers"],"k":[41,38],"o":9},"robot_face":{"a":"Robot Face","b":"1F916","k":[37,30],"o":8},"signal_strength":{"a":"Antenna with Bars","b":"1F4F6","j":["blue-square","reception","phone","internet","connection","wifi","bluetooth","bars"],"k":[26,45]},"flag-gi":{"a":"Gibraltar Flag","b":"1F1EC-1F1EE","k":[2,14]},"flag-gl":{"a":"Greenland Flag","b":"1F1EC-1F1F1","k":[2,15]},"motor_scooter":{"a":"Motor Scooter","b":"1F6F5","j":["vehicle","vespa","sasha"],"k":[37,20],"o":9},"mailbox":{"a":"Closed Mailbox with Raised Flag","b":"1F4EB","j":["email","inbox","communication"],"k":[26,34]},"vibration_mode":{"a":"Vibration Mode","b":"1F4F3","j":["orange-square","phone"],"k":[26,42]},"hankey":{"a":"Pile of Poo","b":"1F4A9","k":[25,15],"n":["poop","shit"]},"rosette":{"a":"Rosette","b":"1F3F5-FE0F","c":"1F3F5","j":["flower","decoration","military"],"k":[12,20],"o":7},"tumbler_glass":{"a":"Tumbler Glass","b":"1F943","j":["drink","beverage","drunk","alcohol","liquor","booze","bourbon","scotch","whisky","glass","shot"],"k":[41,39],"o":9},"cup_with_straw":{"a":"Cup with Straw","b":"1F964","k":[42,16],"o":10},"flag-gm":{"a":"Gambia Flag","b":"1F1EC-1F1F2","k":[2,16]},"mailbox_closed":{"a":"Closed Mailbox with Lowered Flag","b":"1F4EA","j":["email","communication","inbox"],"k":[26,33]},"mobile_phone_off":{"a":"Mobile Phone off","b":"1F4F4","j":["mute","orange-square","silence","quiet"],"k":[26,43]},"busstop":{"a":"Bus Stop","b":"1F68F","j":["transportation","wait"],"k":[34,23]},"smiley_cat":{"a":"Smiling Cat Face with Open Mouth","b":"1F63A","j":["animal","cats","happy","smile"],"k":[31,30]},"rose":{"a":"Rose","b":"1F339","j":["flowers","valentines","love","spring"],"k":[6,47]},"motorway":{"a":"Motorway","b":"1F6E3-FE0F","c":"1F6E3","j":["road","cupertino","interstate","highway"],"k":[37,11],"o":7},"smile_cat":{"a":"Grinning Cat Face with Smiling Eyes","b":"1F638","j":["animal","cats","smile"],"k":[31,28]},"flag-gn":{"a":"Guinea Flag","b":"1F1EC-1F1F3","k":[2,17]},"wilted_flower":{"a":"Wilted Flower","b":"1F940","j":["plant","nature","flower"],"k":[41,36],"o":9},"mailbox_with_mail":{"a":"Open Mailbox with Raised Flag","b":"1F4EC","j":["email","inbox","communication"],"k":[26,35]},"chopsticks":{"a":"Chopsticks","b":"1F962","k":[42,14],"o":10},"female_sign":{"a":"Female Sign","b":"2640-FE0F","c":"2640","k":[47,42],"o":1},"mailbox_with_no_mail":{"a":"Open Mailbox with Lowered Flag","b":"1F4ED","j":["email","inbox"],"k":[26,36]},"knife_fork_plate":{"a":"Knife Fork Plate","b":"1F37D-FE0F","c":"1F37D","k":[8,11],"o":7},"hibiscus":{"a":"Hibiscus","b":"1F33A","j":["plant","vegetable","flowers","beach"],"k":[6,48]},"railway_track":{"a":"Railway Track","b":"1F6E4-FE0F","c":"1F6E4","j":["train","transportation"],"k":[37,12],"o":7},"male_sign":{"a":"Male Sign","b":"2642-FE0F","c":"2642","k":[47,43],"o":1},"joy_cat":{"a":"Cat Face with Tears of Joy","b":"1F639","j":["animal","cats","haha","happy","tears"],"k":[31,29]},"fuelpump":{"a":"Fuel Pump","b":"26FD","j":["gas station","petroleum"],"k":[49,13],"o":5},"sunflower":{"a":"Sunflower","b":"1F33B","j":["nature","plant","fall"],"k":[6,49]},"postbox":{"a":"Postbox","b":"1F4EE","j":["email","letter","envelope"],"k":[26,37]},"flag-gq":{"a":"Equatorial Guinea Flag","b":"1F1EC-1F1F6","k":[2,19]},"heart_eyes_cat":{"a":"Smiling Cat Face with Heart-Shaped Eyes","b":"1F63B","j":["animal","love","like","affection","cats","valentines","heart"],"k":[31,31]},"fork_and_knife":{"a":"Fork and Knife","b":"1F374","j":["cutlery","kitchen"],"k":[8,2]},"medical_symbol":{"a":"Medical Symbol","b":"2695-FE0F","c":"2695","k":[48,14],"n":["staff_of_aesculapius"],"o":4},"recycle":{"a":"Black Universal Recycling Symbol","b":"267B-FE0F","c":"267B","j":["arrow","environment","garbage","trash"],"k":[48,9],"o":3},"spoon":{"a":"Spoon","b":"1F944","j":["cutlery","kitchen","tableware"],"k":[41,40],"o":9},"blossom":{"a":"Blossom","b":"1F33C","j":["nature","flowers","yellow"],"k":[6,50]},"rotating_light":{"a":"Police Cars Revolving Light","b":"1F6A8","j":["police","ambulance","911","emergency","alert","error","pinged","law","legal"],"k":[35,13]},"smirk_cat":{"a":"Cat Face with Wry Smile","b":"1F63C","j":["animal","cats","smirk"],"k":[31,32]},"ballot_box_with_ballot":{"a":"Ballot Box with Ballot","b":"1F5F3-FE0F","c":"1F5F3","k":[30,17],"o":7},"flag-gr":{"a":"Greece Flag","b":"1F1EC-1F1F7","k":[2,20]},"kissing_cat":{"a":"Kissing Cat Face with Closed Eyes","b":"1F63D","j":["animal","cats","kiss"],"k":[31,33]},"pencil2":{"a":"Pencil","b":"270F-FE0F","c":"270F","j":["stationery","write","paper","writing","school","study"],"k":[49,42],"o":1},"traffic_light":{"a":"Horizontal Traffic Light","b":"1F6A5","j":["transportation","signal"],"k":[35,10]},"fleur_de_lis":{"a":"Fleur De Lis","b":"269C-FE0F","c":"269C","j":["decorative","scout"],"k":[48,19],"o":4},"tulip":{"a":"Tulip","b":"1F337","j":["flowers","plant","nature","summer","spring"],"k":[6,45]},"hocho":{"a":"Hocho","b":"1F52A","j":["knife","blade","cutlery","kitchen","weapon"],"k":[27,44],"n":["knife"]},"seedling":{"a":"Seedling","b":"1F331","j":["plant","nature","grass","lawn","spring"],"k":[6,39]},"amphora":{"a":"Amphora","b":"1F3FA","j":["vase","jar"],"k":[12,24],"o":8},"scream_cat":{"a":"Weary Cat Face","b":"1F640","j":["animal","cats","munch","scared","scream"],"k":[31,36]},"vertical_traffic_light":{"a":"Vertical Traffic Light","b":"1F6A6","j":["transportation","driving"],"k":[35,11]},"black_nib":{"a":"Black Nib","b":"2712-FE0F","c":"2712","j":["pen","stationery","writing","write"],"k":[49,43],"o":1},"flag-gt":{"a":"Guatemala Flag","b":"1F1EC-1F1F9","k":[2,22]},"trident":{"a":"Trident Emblem","b":"1F531","j":["weapon","spear"],"k":[27,51]},"flag-gu":{"a":"Guam Flag","b":"1F1EC-1F1FA","k":[2,23]},"name_badge":{"a":"Name Badge","b":"1F4DB","j":["fire","forbid"],"k":[26,18]},"construction":{"a":"Construction Sign","b":"1F6A7","j":["wip","progress","caution","warning"],"k":[35,12]},"lower_left_fountain_pen":{"a":"Lower Left Fountain Pen","b":"1F58B-FE0F","c":"1F58B","k":[29,29],"o":7},"evergreen_tree":{"a":"Evergreen Tree","b":"1F332","j":["plant","nature"],"k":[6,40]},"crying_cat_face":{"a":"Crying Cat Face","b":"1F63F","j":["animal","tears","weep","sad","cats","upset","cry"],"k":[31,35]},"flag-gw":{"a":"Guinea-Bissau Flag","b":"1F1EC-1F1FC","k":[2,24]},"lower_left_ballpoint_pen":{"a":"Lower Left Ballpoint Pen","b":"1F58A-FE0F","c":"1F58A","k":[29,28],"o":7},"pouting_cat":{"a":"Pouting Cat Face","b":"1F63E","j":["animal","cats"],"k":[31,34]},"deciduous_tree":{"a":"Deciduous Tree","b":"1F333","j":["plant","nature"],"k":[6,41]},"octagonal_sign":{"a":"Octagonal Sign","b":"1F6D1","k":[37,6],"o":9},"beginner":{"a":"Japanese Symbol for Beginner","b":"1F530","j":["badge","shield"],"k":[27,50]},"flag-gy":{"a":"Guyana Flag","b":"1F1EC-1F1FE","k":[2,25]},"lower_left_paintbrush":{"a":"Lower Left Paintbrush","b":"1F58C-FE0F","c":"1F58C","k":[29,30],"o":7},"o":{"a":"Heavy Large Circle","b":"2B55","j":["circle","round"],"k":[50,23],"o":5},"palm_tree":{"a":"Palm Tree","b":"1F334","j":["plant","vegetable","nature","summer","beach","mojito","tropical"],"k":[6,42]},"anchor":{"a":"Anchor","b":"2693","j":["ship","ferry","sea","boat"],"k":[48,12],"o":4},"see_no_evil":{"a":"See-No-Evil Monkey","b":"1F648","j":["monkey","animal","nature","haha"],"k":[32,43]},"boat":{"a":"Sailboat","b":"26F5","k":[48,43],"n":["sailboat"],"o":5},"white_check_mark":{"a":"White Heavy Check Mark","b":"2705","j":["green-square","ok","agree","vote","election","answer","tick"],"k":[49,15]},"flag-hk":{"a":"Hong Kong Sar China Flag","b":"1F1ED-1F1F0","k":[2,26]},"lower_left_crayon":{"a":"Lower Left Crayon","b":"1F58D-FE0F","c":"1F58D","k":[29,31],"o":7},"hear_no_evil":{"a":"Hear-No-Evil Monkey","b":"1F649","j":["animal","monkey","nature"],"k":[32,44]},"cactus":{"a":"Cactus","b":"1F335","j":["vegetable","plant","nature"],"k":[6,43]},"ear_of_rice":{"a":"Ear of Rice","b":"1F33E","j":["nature","plant"],"k":[7,0]},"speak_no_evil":{"a":"Speak-No-Evil Monkey","b":"1F64A","j":["monkey","animal","nature","omg"],"k":[32,45]},"flag-hm":{"a":"Heard & Mcdonald Islands Flag","b":"1F1ED-1F1F2","k":[2,27]},"ballot_box_with_check":{"a":"Ballot Box with Check","b":"2611-FE0F","c":"2611","j":["ok","agree","confirm","black-square","vote","election","yes","tick"],"k":[47,22],"o":1},"canoe":{"a":"Canoe","b":"1F6F6","j":["boat","paddle","water","ship"],"k":[37,21],"o":9},"memo":{"a":"Memo","b":"1F4DD","j":["write","documents","stationery","pencil","paper","writing","legal","exam","quiz","test","study","compose"],"k":[26,20],"n":["pencil"]},"herb":{"a":"Herb","b":"1F33F","j":["vegetable","plant","medicine","weed","grass","lawn"],"k":[7,1]},"flag-hn":{"a":"Honduras Flag","b":"1F1ED-1F1F3","k":[2,28]},"heavy_check_mark":{"a":"Heavy Check Mark","b":"2714-FE0F","c":"2714","j":["ok","nike","answer","yes","tick"],"k":[49,44],"o":1},"briefcase":{"a":"Briefcase","b":"1F4BC","j":["business","documents","work","law","legal","job","career"],"k":[25,39]},"speedboat":{"a":"Speedboat","b":"1F6A4","j":["ship","transportation","vehicle","summer"],"k":[35,9]},"baby":{"skin_variations":{"1F3FB":{"unified":"1F476-1F3FB","non_qualified":null,"image":"1f476-1f3fb.png","sheet_x":22,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F476-1F3FC","non_qualified":null,"image":"1f476-1f3fc.png","sheet_x":22,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F476-1F3FD","non_qualified":null,"image":"1f476-1f3fd.png","sheet_x":22,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F476-1F3FE","non_qualified":null,"image":"1f476-1f3fe.png","sheet_x":22,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F476-1F3FF","non_qualified":null,"image":"1f476-1f3ff.png","sheet_x":22,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Baby","b":"1F476","j":["child","boy","girl","toddler"],"k":[22,10]},"heavy_multiplication_x":{"a":"Heavy Multiplication X","b":"2716-FE0F","c":"2716","j":["math","calculation"],"k":[49,45],"o":1},"child":{"skin_variations":{"1F3FB":{"unified":"1F9D2-1F3FB","non_qualified":null,"image":"1f9d2-1f3fb.png","sheet_x":43,"sheet_y":5,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F9D2-1F3FC","non_qualified":null,"image":"1f9d2-1f3fc.png","sheet_x":43,"sheet_y":6,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F9D2-1F3FD","non_qualified":null,"image":"1f9d2-1f3fd.png","sheet_x":43,"sheet_y":7,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F9D2-1F3FE","non_qualified":null,"image":"1f9d2-1f3fe.png","sheet_x":43,"sheet_y":8,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F9D2-1F3FF","non_qualified":null,"image":"1f9d2-1f3ff.png","sheet_x":43,"sheet_y":9,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Child","b":"1F9D2","k":[43,4],"o":10},"shamrock":{"a":"Shamrock","b":"2618-FE0F","c":"2618","j":["vegetable","plant","nature","irish","clover"],"k":[47,25],"o":4},"passenger_ship":{"a":"Passenger Ship","b":"1F6F3-FE0F","c":"1F6F3","j":["yacht","cruise","ferry"],"k":[37,18],"o":7},"flag-hr":{"a":"Croatia Flag","b":"1F1ED-1F1F7","k":[2,29]},"file_folder":{"a":"File Folder","b":"1F4C1","j":["documents","business","office"],"k":[25,44]},"x":{"a":"Cross Mark","b":"274C","j":["no","delete","remove","cancel"],"k":[50,1]},"four_leaf_clover":{"a":"Four Leaf Clover","b":"1F340","j":["vegetable","plant","nature","lucky","irish"],"k":[7,2]},"open_file_folder":{"a":"Open File Folder","b":"1F4C2","j":["documents","load"],"k":[25,45]},"boy":{"skin_variations":{"1F3FB":{"unified":"1F466-1F3FB","non_qualified":null,"image":"1f466-1f3fb.png","sheet_x":15,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F466-1F3FC","non_qualified":null,"image":"1f466-1f3fc.png","sheet_x":15,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F466-1F3FD","non_qualified":null,"image":"1f466-1f3fd.png","sheet_x":15,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F466-1F3FE","non_qualified":null,"image":"1f466-1f3fe.png","sheet_x":15,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F466-1F3FF","non_qualified":null,"image":"1f466-1f3ff.png","sheet_x":15,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Boy","b":"1F466","j":["man","male","guy","teenager"],"k":[15,42]},"ferry":{"a":"Ferry","b":"26F4-FE0F","c":"26F4","j":["boat","ship","yacht"],"k":[48,42],"o":5},"flag-ht":{"a":"Haiti Flag","b":"1F1ED-1F1F9","k":[2,30]},"girl":{"skin_variations":{"1F3FB":{"unified":"1F467-1F3FB","non_qualified":null,"image":"1f467-1f3fb.png","sheet_x":15,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F467-1F3FC","non_qualified":null,"image":"1f467-1f3fc.png","sheet_x":15,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F467-1F3FD","non_qualified":null,"image":"1f467-1f3fd.png","sheet_x":15,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F467-1F3FE","non_qualified":null,"image":"1f467-1f3fe.png","sheet_x":16,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F467-1F3FF","non_qualified":null,"image":"1f467-1f3ff.png","sheet_x":16,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Girl","b":"1F467","j":["female","woman","teenager"],"k":[15,48]},"negative_squared_cross_mark":{"a":"Negative Squared Cross Mark","b":"274E","j":["x","green-square","no","deny"],"k":[50,2]},"flag-hu":{"a":"Hungary Flag","b":"1F1ED-1F1FA","k":[2,31]},"card_index_dividers":{"a":"Card Index Dividers","b":"1F5C2-FE0F","c":"1F5C2","j":["organizing","business","stationery"],"k":[30,4],"o":7},"maple_leaf":{"a":"Maple Leaf","b":"1F341","j":["nature","plant","vegetable","ca","fall"],"k":[7,3]},"motor_boat":{"a":"Motor Boat","b":"1F6E5-FE0F","c":"1F6E5","j":["ship"],"k":[37,13],"o":7},"flag-ic":{"a":"Canary Islands Flag","b":"1F1EE-1F1E8","k":[2,32]},"fallen_leaf":{"a":"Fallen Leaf","b":"1F342","j":["nature","plant","vegetable","leaves"],"k":[7,4]},"adult":{"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB","non_qualified":null,"image":"1f9d1-1f3fb.png","sheet_x":42,"sheet_y":51,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F9D1-1F3FC","non_qualified":null,"image":"1f9d1-1f3fc.png","sheet_x":43,"sheet_y":0,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F9D1-1F3FD","non_qualified":null,"image":"1f9d1-1f3fd.png","sheet_x":43,"sheet_y":1,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F9D1-1F3FE","non_qualified":null,"image":"1f9d1-1f3fe.png","sheet_x":43,"sheet_y":2,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F9D1-1F3FF","non_qualified":null,"image":"1f9d1-1f3ff.png","sheet_x":43,"sheet_y":3,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Adult","b":"1F9D1","k":[42,50],"o":10},"ship":{"a":"Ship","b":"1F6A2","j":["transportation","titanic","deploy"],"k":[34,42]},"heavy_plus_sign":{"a":"Heavy Plus Sign","b":"2795","j":["math","calculation","addition","more","increase"],"k":[50,9]},"date":{"a":"Calendar","b":"1F4C5","j":["calendar","schedule"],"k":[25,48]},"man":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB","non_qualified":null,"image":"1f468-1f3fb.png","sheet_x":18,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F468-1F3FC","non_qualified":null,"image":"1f468-1f3fc.png","sheet_x":18,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F468-1F3FD","non_qualified":null,"image":"1f468-1f3fd.png","sheet_x":18,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F468-1F3FE","non_qualified":null,"image":"1f468-1f3fe.png","sheet_x":18,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F468-1F3FF","non_qualified":null,"image":"1f468-1f3ff.png","sheet_x":18,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Man","b":"1F468","j":["mustache","father","dad","guy","classy","sir","moustache"],"k":[18,11]},"flag-id":{"a":"Indonesia Flag","b":"1F1EE-1F1E9","k":[2,33]},"leaves":{"a":"Leaf Fluttering in Wind","b":"1F343","j":["nature","plant","tree","vegetable","grass","lawn","spring"],"k":[7,5]},"heavy_minus_sign":{"a":"Heavy Minus Sign","b":"2796","j":["math","calculation","subtract","less"],"k":[50,10]},"calendar":{"a":"Tear-off Calendar","b":"1F4C6","j":["schedule","date","planning"],"k":[25,49]},"airplane":{"a":"Airplane","b":"2708-FE0F","c":"2708","j":["vehicle","transportation","flight","fly"],"k":[49,16],"o":1},"spiral_note_pad":{"a":"Spiral Note Pad","b":"1F5D2-FE0F","c":"1F5D2","k":[30,8],"o":7},"heavy_division_sign":{"a":"Heavy Division Sign","b":"2797","j":["divide","math","calculation"],"k":[50,11]},"small_airplane":{"a":"Small Airplane","b":"1F6E9-FE0F","c":"1F6E9","j":["flight","transportation","fly","vehicle"],"k":[37,14],"o":7},"woman":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB","non_qualified":null,"image":"1f469-1f3fb.png","sheet_x":20,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F469-1F3FC","non_qualified":null,"image":"1f469-1f3fc.png","sheet_x":20,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F469-1F3FD","non_qualified":null,"image":"1f469-1f3fd.png","sheet_x":20,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F469-1F3FE","non_qualified":null,"image":"1f469-1f3fe.png","sheet_x":20,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F469-1F3FF","non_qualified":null,"image":"1f469-1f3ff.png","sheet_x":20,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Woman","b":"1F469","j":["female","girls","lady"],"k":[20,23]},"flag-ie":{"a":"Ireland Flag","b":"1F1EE-1F1EA","k":[2,34]},"curly_loop":{"a":"Curly Loop","b":"27B0","j":["scribble","draw","shape","squiggle"],"k":[50,13]},"flag-il":{"a":"Israel Flag","b":"1F1EE-1F1F1","k":[2,35]},"airplane_departure":{"a":"Airplane Departure","b":"1F6EB","k":[37,15],"o":7},"spiral_calendar_pad":{"a":"Spiral Calendar Pad","b":"1F5D3-FE0F","c":"1F5D3","k":[30,9],"o":7},"older_adult":{"skin_variations":{"1F3FB":{"unified":"1F9D3-1F3FB","non_qualified":null,"image":"1f9d3-1f3fb.png","sheet_x":43,"sheet_y":11,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F9D3-1F3FC","non_qualified":null,"image":"1f9d3-1f3fc.png","sheet_x":43,"sheet_y":12,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F9D3-1F3FD","non_qualified":null,"image":"1f9d3-1f3fd.png","sheet_x":43,"sheet_y":13,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F9D3-1F3FE","non_qualified":null,"image":"1f9d3-1f3fe.png","sheet_x":43,"sheet_y":14,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F9D3-1F3FF","non_qualified":null,"image":"1f9d3-1f3ff.png","sheet_x":43,"sheet_y":15,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Older Adult","b":"1F9D3","k":[43,10],"o":10},"airplane_arriving":{"a":"Airplane Arriving","b":"1F6EC","k":[37,16],"o":7},"card_index":{"a":"Card Index","b":"1F4C7","j":["business","stationery"],"k":[25,50]},"loop":{"a":"Double Curly Loop","b":"27BF","j":["tape","cassette"],"k":[50,14]},"older_man":{"skin_variations":{"1F3FB":{"unified":"1F474-1F3FB","non_qualified":null,"image":"1f474-1f3fb.png","sheet_x":21,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F474-1F3FC","non_qualified":null,"image":"1f474-1f3fc.png","sheet_x":22,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F474-1F3FD","non_qualified":null,"image":"1f474-1f3fd.png","sheet_x":22,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F474-1F3FE","non_qualified":null,"image":"1f474-1f3fe.png","sheet_x":22,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F474-1F3FF","non_qualified":null,"image":"1f474-1f3ff.png","sheet_x":22,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Older Man","b":"1F474","j":["human","male","men","old","elder","senior"],"k":[21,50]},"flag-im":{"a":"Isle of Man Flag","b":"1F1EE-1F1F2","k":[2,36]},"flag-in":{"a":"India Flag","b":"1F1EE-1F1F3","k":[2,37]},"chart_with_upwards_trend":{"a":"Chart with Upwards Trend","b":"1F4C8","j":["graph","presentation","stats","recovery","business","economics","money","sales","good","success"],"k":[25,51]},"part_alternation_mark":{"a":"Part Alternation Mark","b":"303D-FE0F","c":"303D","j":["graph","presentation","stats","business","economics","bad"],"k":[50,25],"o":3},"seat":{"a":"Seat","b":"1F4BA","j":["sit","airplane","transport","bus","flight","fly"],"k":[25,37]},"older_woman":{"skin_variations":{"1F3FB":{"unified":"1F475-1F3FB","non_qualified":null,"image":"1f475-1f3fb.png","sheet_x":22,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F475-1F3FC","non_qualified":null,"image":"1f475-1f3fc.png","sheet_x":22,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F475-1F3FD","non_qualified":null,"image":"1f475-1f3fd.png","sheet_x":22,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F475-1F3FE","non_qualified":null,"image":"1f475-1f3fe.png","sheet_x":22,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F475-1F3FF","non_qualified":null,"image":"1f475-1f3ff.png","sheet_x":22,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Older Woman","b":"1F475","j":["human","female","women","lady","old","elder","senior"],"k":[22,4]},"eight_spoked_asterisk":{"a":"Eight Spoked Asterisk","b":"2733-FE0F","c":"2733","j":["star","sparkle","green-square"],"k":[49,49],"o":1},"chart_with_downwards_trend":{"a":"Chart with Downwards Trend","b":"1F4C9","j":["graph","presentation","stats","recession","business","economics","money","sales","bad","failure"],"k":[26,0]},"flag-io":{"a":"British Indian Ocean Territory Flag","b":"1F1EE-1F1F4","k":[2,38]},"male-doctor":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-2695-FE0F","non_qualified":"1F468-1F3FB-200D-2695","image":"1f468-1f3fb-200d-2695-fe0f.png","sheet_x":17,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-2695-FE0F","non_qualified":"1F468-1F3FC-200D-2695","image":"1f468-1f3fc-200d-2695-fe0f.png","sheet_x":17,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-2695-FE0F","non_qualified":"1F468-1F3FD-200D-2695","image":"1f468-1f3fd-200d-2695-fe0f.png","sheet_x":17,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-2695-FE0F","non_qualified":"1F468-1F3FE-200D-2695","image":"1f468-1f3fe-200d-2695-fe0f.png","sheet_x":17,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-2695-FE0F","non_qualified":"1F468-1F3FF-200D-2695","image":"1f468-1f3ff-200d-2695-fe0f.png","sheet_x":17,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Male Doctor","b":"1F468-200D-2695-FE0F","c":"1F468-200D-2695","k":[17,43]},"helicopter":{"a":"Helicopter","b":"1F681","j":["transportation","vehicle","fly"],"k":[34,9]},"female-doctor":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-2695-FE0F","non_qualified":"1F469-1F3FB-200D-2695","image":"1f469-1f3fb-200d-2695-fe0f.png","sheet_x":20,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-2695-FE0F","non_qualified":"1F469-1F3FC-200D-2695","image":"1f469-1f3fc-200d-2695-fe0f.png","sheet_x":20,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-2695-FE0F","non_qualified":"1F469-1F3FD-200D-2695","image":"1f469-1f3fd-200d-2695-fe0f.png","sheet_x":20,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-2695-FE0F","non_qualified":"1F469-1F3FE-200D-2695","image":"1f469-1f3fe-200d-2695-fe0f.png","sheet_x":20,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-2695-FE0F","non_qualified":"1F469-1F3FF-200D-2695","image":"1f469-1f3ff-200d-2695-fe0f.png","sheet_x":20,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Female Doctor","b":"1F469-200D-2695-FE0F","c":"1F469-200D-2695","k":[20,1]},"suspension_railway":{"a":"Suspension Railway","b":"1F69F","j":["vehicle","transportation"],"k":[34,39]},"bar_chart":{"a":"Bar Chart","b":"1F4CA","j":["graph","presentation","stats"],"k":[26,1]},"flag-iq":{"a":"Iraq Flag","b":"1F1EE-1F1F6","k":[2,39]},"eight_pointed_black_star":{"a":"Eight Pointed Black Star","b":"2734-FE0F","c":"2734","j":["orange-square","shape","polygon"],"k":[49,50],"o":1},"mountain_cableway":{"a":"Mountain Cableway","b":"1F6A0","j":["transportation","vehicle","ski"],"k":[34,40]},"male-student":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F393","non_qualified":null,"image":"1f468-1f3fb-200d-1f393.png","sheet_x":16,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F393","non_qualified":null,"image":"1f468-1f3fc-200d-1f393.png","sheet_x":16,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F393","non_qualified":null,"image":"1f468-1f3fd-200d-1f393.png","sheet_x":16,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F393","non_qualified":null,"image":"1f468-1f3fe-200d-1f393.png","sheet_x":16,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F393","non_qualified":null,"image":"1f468-1f3ff-200d-1f393.png","sheet_x":16,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Student","b":"1F468-200D-1F393","k":[16,14]},"clipboard":{"a":"Clipboard","b":"1F4CB","j":["stationery","documents"],"k":[26,2]},"flag-ir":{"a":"Iran Flag","b":"1F1EE-1F1F7","k":[2,40]},"sparkle":{"a":"Sparkle","b":"2747-FE0F","c":"2747","j":["stars","green-square","awesome","good","fireworks"],"k":[50,0],"o":1},"female-student":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F393","non_qualified":null,"image":"1f469-1f3fb-200d-1f393.png","sheet_x":18,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F393","non_qualified":null,"image":"1f469-1f3fc-200d-1f393.png","sheet_x":18,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F393","non_qualified":null,"image":"1f469-1f3fd-200d-1f393.png","sheet_x":18,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F393","non_qualified":null,"image":"1f469-1f3fe-200d-1f393.png","sheet_x":18,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F393","non_qualified":null,"image":"1f469-1f3ff-200d-1f393.png","sheet_x":18,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Student","b":"1F469-200D-1F393","k":[18,29]},"pushpin":{"a":"Pushpin","b":"1F4CC","j":["stationery","mark","here"],"k":[26,3]},"aerial_tramway":{"a":"Aerial Tramway","b":"1F6A1","j":["transportation","vehicle","ski"],"k":[34,41]},"flag-is":{"a":"Iceland Flag","b":"1F1EE-1F1F8","k":[2,41]},"bangbang":{"a":"Double Exclamation Mark","b":"203C-FE0F","c":"203C","j":["exclamation","surprise"],"k":[46,29],"o":1},"interrobang":{"a":"Exclamation Question Mark","b":"2049-FE0F","c":"2049","j":["wat","punctuation","surprise"],"k":[46,30],"o":3},"satellite":{"a":"Satellite","b":"1F6F0-FE0F","c":"1F6F0","j":["communication","future","radio","space"],"k":[37,17],"o":7},"it":{"a":"Italy Flag","b":"1F1EE-1F1F9","j":["italy","flag","nation","country","banner"],"k":[2,42],"n":["flag-it"]},"male-teacher":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3EB","non_qualified":null,"image":"1f468-1f3fb-200d-1f3eb.png","sheet_x":16,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3EB","non_qualified":null,"image":"1f468-1f3fc-200d-1f3eb.png","sheet_x":16,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3EB","non_qualified":null,"image":"1f468-1f3fd-200d-1f3eb.png","sheet_x":16,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3EB","non_qualified":null,"image":"1f468-1f3fe-200d-1f3eb.png","sheet_x":16,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3EB","non_qualified":null,"image":"1f468-1f3ff-200d-1f3eb.png","sheet_x":16,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Teacher","b":"1F468-200D-1F3EB","k":[16,32]},"round_pushpin":{"a":"Round Pushpin","b":"1F4CD","j":["stationery","location","map","here"],"k":[26,4]},"flag-je":{"a":"Jersey Flag","b":"1F1EF-1F1EA","k":[2,43]},"question":{"a":"Black Question Mark Ornament","b":"2753","j":["doubt","confused"],"k":[50,3]},"rocket":{"a":"Rocket","b":"1F680","j":["launch","ship","staffmode","NASA","outer space","outer_space","fly"],"k":[34,8]},"female-teacher":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3EB","non_qualified":null,"image":"1f469-1f3fb-200d-1f3eb.png","sheet_x":18,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3EB","non_qualified":null,"image":"1f469-1f3fc-200d-1f3eb.png","sheet_x":18,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3EB","non_qualified":null,"image":"1f469-1f3fd-200d-1f3eb.png","sheet_x":18,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3EB","non_qualified":null,"image":"1f469-1f3fe-200d-1f3eb.png","sheet_x":18,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3EB","non_qualified":null,"image":"1f469-1f3ff-200d-1f3eb.png","sheet_x":19,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Teacher","b":"1F469-200D-1F3EB","k":[18,47]},"paperclip":{"a":"Paperclip","b":"1F4CE","j":["documents","stationery"],"k":[26,5]},"linked_paperclips":{"a":"Linked Paperclips","b":"1F587-FE0F","c":"1F587","k":[29,27],"o":7},"flying_saucer":{"a":"Flying Saucer","b":"1F6F8","k":[37,23],"o":10},"male-judge":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-2696-FE0F","non_qualified":"1F468-1F3FB-200D-2696","image":"1f468-1f3fb-200d-2696-fe0f.png","sheet_x":17,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-2696-FE0F","non_qualified":"1F468-1F3FC-200D-2696","image":"1f468-1f3fc-200d-2696-fe0f.png","sheet_x":17,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-2696-FE0F","non_qualified":"1F468-1F3FD-200D-2696","image":"1f468-1f3fd-200d-2696-fe0f.png","sheet_x":18,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-2696-FE0F","non_qualified":"1F468-1F3FE-200D-2696","image":"1f468-1f3fe-200d-2696-fe0f.png","sheet_x":18,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-2696-FE0F","non_qualified":"1F468-1F3FF-200D-2696","image":"1f468-1f3ff-200d-2696-fe0f.png","sheet_x":18,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Male Judge","b":"1F468-200D-2696-FE0F","c":"1F468-200D-2696","k":[17,49]},"grey_question":{"a":"White Question Mark Ornament","b":"2754","j":["doubts","gray","huh","confused"],"k":[50,4]},"flag-jm":{"a":"Jamaica Flag","b":"1F1EF-1F1F2","k":[2,44]},"bellhop_bell":{"a":"Bellhop Bell","b":"1F6CE-FE0F","c":"1F6CE","j":["service"],"k":[37,3],"o":7},"straight_ruler":{"a":"Straight Ruler","b":"1F4CF","j":["stationery","calculate","length","math","school","drawing","architect","sketch"],"k":[26,6]},"flag-jo":{"a":"Jordan Flag","b":"1F1EF-1F1F4","k":[2,45]},"female-judge":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-2696-FE0F","non_qualified":"1F469-1F3FB-200D-2696","image":"1f469-1f3fb-200d-2696-fe0f.png","sheet_x":20,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-2696-FE0F","non_qualified":"1F469-1F3FC-200D-2696","image":"1f469-1f3fc-200d-2696-fe0f.png","sheet_x":20,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-2696-FE0F","non_qualified":"1F469-1F3FD-200D-2696","image":"1f469-1f3fd-200d-2696-fe0f.png","sheet_x":20,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-2696-FE0F","non_qualified":"1F469-1F3FE-200D-2696","image":"1f469-1f3fe-200d-2696-fe0f.png","sheet_x":20,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-2696-FE0F","non_qualified":"1F469-1F3FF-200D-2696","image":"1f469-1f3ff-200d-2696-fe0f.png","sheet_x":20,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Female Judge","b":"1F469-200D-2696-FE0F","c":"1F469-200D-2696","k":[20,7]},"grey_exclamation":{"a":"White Exclamation Mark Ornament","b":"2755","j":["surprise","punctuation","gray","wow","warning"],"k":[50,5]},"door":{"a":"Door","b":"1F6AA","j":["house","entry","exit"],"k":[35,15]},"male-farmer":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F33E","non_qualified":null,"image":"1f468-1f3fb-200d-1f33e.png","sheet_x":16,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F33E","non_qualified":null,"image":"1f468-1f3fc-200d-1f33e.png","sheet_x":16,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F33E","non_qualified":null,"image":"1f468-1f3fd-200d-1f33e.png","sheet_x":16,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F33E","non_qualified":null,"image":"1f468-1f3fe-200d-1f33e.png","sheet_x":16,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F33E","non_qualified":null,"image":"1f468-1f3ff-200d-1f33e.png","sheet_x":16,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Farmer","b":"1F468-200D-1F33E","k":[16,2]},"jp":{"a":"Japan Flag","b":"1F1EF-1F1F5","j":["japanese","nation","flag","country","banner"],"k":[2,46],"n":["flag-jp"]},"triangular_ruler":{"a":"Triangular Ruler","b":"1F4D0","j":["stationery","math","architect","sketch"],"k":[26,7]},"exclamation":{"a":"Heavy Exclamation Mark Symbol","b":"2757","j":["heavy_exclamation_mark","danger","surprise","punctuation","wow","warning"],"k":[50,6],"n":["heavy_exclamation_mark"],"o":5},"bed":{"a":"Bed","b":"1F6CF-FE0F","c":"1F6CF","j":["sleep","rest"],"k":[37,4],"o":7},"female-farmer":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F33E","non_qualified":null,"image":"1f469-1f3fb-200d-1f33e.png","sheet_x":18,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F33E","non_qualified":null,"image":"1f469-1f3fc-200d-1f33e.png","sheet_x":18,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F33E","non_qualified":null,"image":"1f469-1f3fd-200d-1f33e.png","sheet_x":18,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F33E","non_qualified":null,"image":"1f469-1f3fe-200d-1f33e.png","sheet_x":18,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F33E","non_qualified":null,"image":"1f469-1f3ff-200d-1f33e.png","sheet_x":18,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Farmer","b":"1F469-200D-1F33E","k":[18,17]},"scissors":{"a":"Black Scissors","b":"2702-FE0F","c":"2702","j":["stationery","cut"],"k":[49,14],"o":1},"wavy_dash":{"a":"Wavy Dash","b":"3030-FE0F","c":"3030","j":["draw","line","moustache","mustache","squiggle","scribble"],"k":[50,24],"o":1},"flag-ke":{"a":"Kenya Flag","b":"1F1F0-1F1EA","k":[2,47]},"flag-kg":{"a":"Kyrgyzstan Flag","b":"1F1F0-1F1EC","k":[2,48]},"couch_and_lamp":{"a":"Couch and Lamp","b":"1F6CB-FE0F","c":"1F6CB","j":["read","chill"],"k":[36,47],"o":7},"male-cook":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F373","non_qualified":null,"image":"1f468-1f3fb-200d-1f373.png","sheet_x":16,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F373","non_qualified":null,"image":"1f468-1f3fc-200d-1f373.png","sheet_x":16,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F373","non_qualified":null,"image":"1f468-1f3fd-200d-1f373.png","sheet_x":16,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F373","non_qualified":null,"image":"1f468-1f3fe-200d-1f373.png","sheet_x":16,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F373","non_qualified":null,"image":"1f468-1f3ff-200d-1f373.png","sheet_x":16,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Cook","b":"1F468-200D-1F373","k":[16,8]},"card_file_box":{"a":"Card File Box","b":"1F5C3-FE0F","c":"1F5C3","j":["business","stationery"],"k":[30,5],"o":7},"copyright":{"a":"Copyright Sign","b":"00A9-FE0F","c":"00A9","j":["ip","license","circle","law","legal"],"k":[0,12],"o":1},"file_cabinet":{"a":"File Cabinet","b":"1F5C4-FE0F","c":"1F5C4","j":["filing","organizing"],"k":[30,6],"o":7},"registered":{"a":"Registered Sign","b":"00AE-FE0F","c":"00AE","j":["alphabet","circle"],"k":[0,13],"o":1},"flag-kh":{"a":"Cambodia Flag","b":"1F1F0-1F1ED","k":[2,49]},"female-cook":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F373","non_qualified":null,"image":"1f469-1f3fb-200d-1f373.png","sheet_x":18,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F373","non_qualified":null,"image":"1f469-1f3fc-200d-1f373.png","sheet_x":18,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F373","non_qualified":null,"image":"1f469-1f3fd-200d-1f373.png","sheet_x":18,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F373","non_qualified":null,"image":"1f469-1f3fe-200d-1f373.png","sheet_x":18,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F373","non_qualified":null,"image":"1f469-1f3ff-200d-1f373.png","sheet_x":18,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Cook","b":"1F469-200D-1F373","k":[18,23]},"toilet":{"a":"Toilet","b":"1F6BD","j":["restroom","wc","washroom","bathroom","potty"],"k":[36,33]},"wastebasket":{"a":"Wastebasket","b":"1F5D1-FE0F","c":"1F5D1","j":["bin","trash","rubbish","garbage","toss"],"k":[30,7],"o":7},"flag-ki":{"a":"Kiribati Flag","b":"1F1F0-1F1EE","k":[2,50]},"shower":{"a":"Shower","b":"1F6BF","j":["clean","water","bathroom"],"k":[36,35]},"male-mechanic":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F527","non_qualified":null,"image":"1f468-1f3fb-200d-1f527.png","sheet_x":17,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F527","non_qualified":null,"image":"1f468-1f3fc-200d-1f527.png","sheet_x":17,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F527","non_qualified":null,"image":"1f468-1f3fd-200d-1f527.png","sheet_x":17,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F527","non_qualified":null,"image":"1f468-1f3fe-200d-1f527.png","sheet_x":17,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F527","non_qualified":null,"image":"1f468-1f3ff-200d-1f527.png","sheet_x":17,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Mechanic","b":"1F468-200D-1F527","k":[17,19]},"tm":{"a":"Trade Mark Sign","b":"2122-FE0F","c":"2122","j":["trademark","brand","law","legal"],"k":[46,31],"o":1},"hash":{"a":"Hash Key","b":"0023-FE0F-20E3","c":"0023-20E3","j":["symbol","blue-square","twitter"],"k":[0,0],"o":3},"flag-km":{"a":"Comoros Flag","b":"1F1F0-1F1F2","k":[2,51]},"bathtub":{"a":"Bathtub","b":"1F6C1","j":["clean","shower","bathroom"],"k":[36,42]},"female-mechanic":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F527","non_qualified":null,"image":"1f469-1f3fb-200d-1f527.png","sheet_x":19,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F527","non_qualified":null,"image":"1f469-1f3fc-200d-1f527.png","sheet_x":19,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F527","non_qualified":null,"image":"1f469-1f3fd-200d-1f527.png","sheet_x":19,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F527","non_qualified":null,"image":"1f469-1f3fe-200d-1f527.png","sheet_x":19,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F527","non_qualified":null,"image":"1f469-1f3ff-200d-1f527.png","sheet_x":19,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Mechanic","b":"1F469-200D-1F527","k":[19,29]},"lock":{"a":"Lock","b":"1F512","j":["security","password","padlock"],"k":[27,20]},"male-factory-worker":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3ED","non_qualified":null,"image":"1f468-1f3fb-200d-1f3ed.png","sheet_x":16,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3ED","non_qualified":null,"image":"1f468-1f3fc-200d-1f3ed.png","sheet_x":16,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3ED","non_qualified":null,"image":"1f468-1f3fd-200d-1f3ed.png","sheet_x":16,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3ED","non_qualified":null,"image":"1f468-1f3fe-200d-1f3ed.png","sheet_x":16,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3ED","non_qualified":null,"image":"1f468-1f3ff-200d-1f3ed.png","sheet_x":16,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Factory Worker","b":"1F468-200D-1F3ED","k":[16,38]},"flag-kn":{"a":"St. Kitts & Nevis Flag","b":"1F1F0-1F1F3","k":[3,0]},"hourglass":{"a":"Hourglass","b":"231B","j":["time","clock","oldschool","limit","exam","quiz","test"],"k":[46,42],"o":1},"keycap_star":{"a":"Keycap Star","b":"002A-FE0F-20E3","c":"002A-20E3","k":[0,1],"o":3},"unlock":{"a":"Open Lock","b":"1F513","j":["privacy","security"],"k":[27,21]},"flag-kp":{"a":"North Korea Flag","b":"1F1F0-1F1F5","k":[3,1]},"female-factory-worker":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3ED","non_qualified":null,"image":"1f469-1f3fb-200d-1f3ed.png","sheet_x":19,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3ED","non_qualified":null,"image":"1f469-1f3fc-200d-1f3ed.png","sheet_x":19,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3ED","non_qualified":null,"image":"1f469-1f3fd-200d-1f3ed.png","sheet_x":19,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3ED","non_qualified":null,"image":"1f469-1f3fe-200d-1f3ed.png","sheet_x":19,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3ED","non_qualified":null,"image":"1f469-1f3ff-200d-1f3ed.png","sheet_x":19,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Factory Worker","b":"1F469-200D-1F3ED","k":[19,1]},"zero":{"a":"Keycap 0","b":"0030-FE0F-20E3","c":"0030-20E3","j":["0","numbers","blue-square","null"],"k":[0,2],"o":3},"lock_with_ink_pen":{"a":"Lock with Ink Pen","b":"1F50F","j":["security","secret"],"k":[27,17]},"hourglass_flowing_sand":{"a":"Hourglass with Flowing Sand","b":"23F3","j":["oldschool","time","countdown"],"k":[47,3]},"one":{"a":"Keycap 1","b":"0031-FE0F-20E3","c":"0031-20E3","j":["blue-square","numbers","1"],"k":[0,3],"o":3},"kr":{"a":"South Korea Flag","b":"1F1F0-1F1F7","j":["south","korea","nation","flag","country","banner"],"k":[3,2],"n":["flag-kr"]},"watch":{"a":"Watch","b":"231A","j":["time","accessories"],"k":[46,41],"o":1},"male-office-worker":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F4BC","non_qualified":null,"image":"1f468-1f3fb-200d-1f4bc.png","sheet_x":17,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F4BC","non_qualified":null,"image":"1f468-1f3fc-200d-1f4bc.png","sheet_x":17,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F4BC","non_qualified":null,"image":"1f468-1f3fd-200d-1f4bc.png","sheet_x":17,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F4BC","non_qualified":null,"image":"1f468-1f3fe-200d-1f4bc.png","sheet_x":17,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F4BC","non_qualified":null,"image":"1f468-1f3ff-200d-1f4bc.png","sheet_x":17,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Office Worker","b":"1F468-200D-1F4BC","k":[17,13]},"closed_lock_with_key":{"a":"Closed Lock with Key","b":"1F510","j":["security","privacy"],"k":[27,18]},"female-office-worker":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F4BC","non_qualified":null,"image":"1f469-1f3fb-200d-1f4bc.png","sheet_x":19,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F4BC","non_qualified":null,"image":"1f469-1f3fc-200d-1f4bc.png","sheet_x":19,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F4BC","non_qualified":null,"image":"1f469-1f3fd-200d-1f4bc.png","sheet_x":19,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F4BC","non_qualified":null,"image":"1f469-1f3fe-200d-1f4bc.png","sheet_x":19,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F4BC","non_qualified":null,"image":"1f469-1f3ff-200d-1f4bc.png","sheet_x":19,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Office Worker","b":"1F469-200D-1F4BC","k":[19,23]},"two":{"a":"Keycap 2","b":"0032-FE0F-20E3","c":"0032-20E3","j":["numbers","2","prime","blue-square"],"k":[0,4],"o":3},"alarm_clock":{"a":"Alarm Clock","b":"23F0","j":["time","wake"],"k":[47,0]},"key":{"a":"Key","b":"1F511","j":["lock","door","password"],"k":[27,19]},"flag-kw":{"a":"Kuwait Flag","b":"1F1F0-1F1FC","k":[3,3]},"stopwatch":{"a":"Stopwatch","b":"23F1-FE0F","c":"23F1","j":["time","deadline"],"k":[47,1]},"male-scientist":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F52C","non_qualified":null,"image":"1f468-1f3fb-200d-1f52c.png","sheet_x":17,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F52C","non_qualified":null,"image":"1f468-1f3fc-200d-1f52c.png","sheet_x":17,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F52C","non_qualified":null,"image":"1f468-1f3fd-200d-1f52c.png","sheet_x":17,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F52C","non_qualified":null,"image":"1f468-1f3fe-200d-1f52c.png","sheet_x":17,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F52C","non_qualified":null,"image":"1f468-1f3ff-200d-1f52c.png","sheet_x":17,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Scientist","b":"1F468-200D-1F52C","k":[17,25]},"three":{"a":"Keycap 3","b":"0033-FE0F-20E3","c":"0033-20E3","j":["3","numbers","prime","blue-square"],"k":[0,5],"o":3},"flag-ky":{"a":"Cayman Islands Flag","b":"1F1F0-1F1FE","k":[3,4]},"old_key":{"a":"Old Key","b":"1F5DD-FE0F","c":"1F5DD","j":["lock","door","password"],"k":[30,11],"o":7},"flag-kz":{"a":"Kazakhstan Flag","b":"1F1F0-1F1FF","k":[3,5]},"hammer":{"a":"Hammer","b":"1F528","j":["tools","build","create"],"k":[27,42]},"female-scientist":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F52C","non_qualified":null,"image":"1f469-1f3fb-200d-1f52c.png","sheet_x":19,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F52C","non_qualified":null,"image":"1f469-1f3fc-200d-1f52c.png","sheet_x":19,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F52C","non_qualified":null,"image":"1f469-1f3fd-200d-1f52c.png","sheet_x":19,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F52C","non_qualified":null,"image":"1f469-1f3fe-200d-1f52c.png","sheet_x":19,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F52C","non_qualified":null,"image":"1f469-1f3ff-200d-1f52c.png","sheet_x":19,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Scientist","b":"1F469-200D-1F52C","k":[19,35]},"timer_clock":{"a":"Timer Clock","b":"23F2-FE0F","c":"23F2","j":["alarm"],"k":[47,2]},"four":{"a":"Keycap 4","b":"0034-FE0F-20E3","c":"0034-20E3","j":["4","numbers","blue-square"],"k":[0,6],"o":3},"male-technologist":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F4BB","non_qualified":null,"image":"1f468-1f3fb-200d-1f4bb.png","sheet_x":17,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F4BB","non_qualified":null,"image":"1f468-1f3fc-200d-1f4bb.png","sheet_x":17,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F4BB","non_qualified":null,"image":"1f468-1f3fd-200d-1f4bb.png","sheet_x":17,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F4BB","non_qualified":null,"image":"1f468-1f3fe-200d-1f4bb.png","sheet_x":17,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F4BB","non_qualified":null,"image":"1f468-1f3ff-200d-1f4bb.png","sheet_x":17,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Technologist","b":"1F468-200D-1F4BB","k":[17,7]},"mantelpiece_clock":{"a":"Mantelpiece Clock","b":"1F570-FE0F","c":"1F570","j":["time"],"k":[28,43],"o":7},"five":{"a":"Keycap 5","b":"0035-FE0F-20E3","c":"0035-20E3","j":["5","numbers","blue-square","prime"],"k":[0,7],"o":3},"flag-la":{"a":"Laos Flag","b":"1F1F1-1F1E6","k":[3,6]},"pick":{"a":"Pick","b":"26CF-FE0F","c":"26CF","j":["tools","dig"],"k":[48,32],"o":5},"flag-lb":{"a":"Lebanon Flag","b":"1F1F1-1F1E7","k":[3,7]},"clock12":{"a":"Clock Face Twelve Oclock","b":"1F55B","j":["time","noon","midnight","midday","late","early","schedule"],"k":[28,29]},"hammer_and_pick":{"a":"Hammer and Pick","b":"2692-FE0F","c":"2692","j":["tools","build","create"],"k":[48,11],"o":4},"six":{"a":"Keycap 6","b":"0036-FE0F-20E3","c":"0036-20E3","j":["6","numbers","blue-square"],"k":[0,8],"o":3},"female-technologist":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F4BB","non_qualified":null,"image":"1f469-1f3fb-200d-1f4bb.png","sheet_x":19,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F4BB","non_qualified":null,"image":"1f469-1f3fc-200d-1f4bb.png","sheet_x":19,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F4BB","non_qualified":null,"image":"1f469-1f3fd-200d-1f4bb.png","sheet_x":19,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F4BB","non_qualified":null,"image":"1f469-1f3fe-200d-1f4bb.png","sheet_x":19,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F4BB","non_qualified":null,"image":"1f469-1f3ff-200d-1f4bb.png","sheet_x":19,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Technologist","b":"1F469-200D-1F4BB","k":[19,17]},"hammer_and_wrench":{"a":"Hammer and Wrench","b":"1F6E0-FE0F","c":"1F6E0","j":["tools","build","create"],"k":[37,8],"o":7},"flag-lc":{"a":"St. Lucia Flag","b":"1F1F1-1F1E8","k":[3,8]},"clock1230":{"a":"Clock Face Twelve-Thirty","b":"1F567","j":["time","late","early","schedule"],"k":[28,41]},"seven":{"a":"Keycap 7","b":"0037-FE0F-20E3","c":"0037-20E3","j":["7","numbers","blue-square","prime"],"k":[0,9],"o":3},"male-singer":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3A4","non_qualified":null,"image":"1f468-1f3fb-200d-1f3a4.png","sheet_x":16,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3A4","non_qualified":null,"image":"1f468-1f3fc-200d-1f3a4.png","sheet_x":16,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3A4","non_qualified":null,"image":"1f468-1f3fd-200d-1f3a4.png","sheet_x":16,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3A4","non_qualified":null,"image":"1f468-1f3fe-200d-1f3a4.png","sheet_x":16,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3A4","non_qualified":null,"image":"1f468-1f3ff-200d-1f3a4.png","sheet_x":16,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Singer","b":"1F468-200D-1F3A4","k":[16,20]},"eight":{"a":"Keycap 8","b":"0038-FE0F-20E3","c":"0038-20E3","j":["8","blue-square","numbers"],"k":[0,10],"o":3},"flag-li":{"a":"Liechtenstein Flag","b":"1F1F1-1F1EE","k":[3,9]},"dagger_knife":{"a":"Dagger Knife","b":"1F5E1-FE0F","c":"1F5E1","k":[30,13],"o":7},"clock1":{"a":"Clock Face One Oclock","b":"1F550","j":["time","late","early","schedule"],"k":[28,18]},"female-singer":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3A4","non_qualified":null,"image":"1f469-1f3fb-200d-1f3a4.png","sheet_x":18,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3A4","non_qualified":null,"image":"1f469-1f3fc-200d-1f3a4.png","sheet_x":18,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3A4","non_qualified":null,"image":"1f469-1f3fd-200d-1f3a4.png","sheet_x":18,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3A4","non_qualified":null,"image":"1f469-1f3fe-200d-1f3a4.png","sheet_x":18,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3A4","non_qualified":null,"image":"1f469-1f3ff-200d-1f3a4.png","sheet_x":18,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Singer","b":"1F469-200D-1F3A4","k":[18,35]},"male-artist":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3A8","non_qualified":null,"image":"1f468-1f3fb-200d-1f3a8.png","sheet_x":16,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3A8","non_qualified":null,"image":"1f468-1f3fc-200d-1f3a8.png","sheet_x":16,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3A8","non_qualified":null,"image":"1f468-1f3fd-200d-1f3a8.png","sheet_x":16,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3A8","non_qualified":null,"image":"1f468-1f3fe-200d-1f3a8.png","sheet_x":16,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3A8","non_qualified":null,"image":"1f468-1f3ff-200d-1f3a8.png","sheet_x":16,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Artist","b":"1F468-200D-1F3A8","k":[16,26]},"crossed_swords":{"a":"Crossed Swords","b":"2694-FE0F","c":"2694","j":["weapon"],"k":[48,13],"o":4},"nine":{"a":"Keycap 9","b":"0039-FE0F-20E3","c":"0039-20E3","j":["blue-square","numbers","9"],"k":[0,11],"o":3},"flag-lk":{"a":"Sri Lanka Flag","b":"1F1F1-1F1F0","k":[3,10]},"clock130":{"a":"Clock Face One-Thirty","b":"1F55C","j":["time","late","early","schedule"],"k":[28,30]},"clock2":{"a":"Clock Face Two Oclock","b":"1F551","j":["time","late","early","schedule"],"k":[28,19]},"gun":{"a":"Pistol","b":"1F52B","j":["violence","weapon","pistol","revolver"],"k":[27,45]},"keycap_ten":{"a":"Keycap Ten","b":"1F51F","j":["numbers","10","blue-square"],"k":[27,33]},"female-artist":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3A8","non_qualified":null,"image":"1f469-1f3fb-200d-1f3a8.png","sheet_x":18,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3A8","non_qualified":null,"image":"1f469-1f3fc-200d-1f3a8.png","sheet_x":18,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3A8","non_qualified":null,"image":"1f469-1f3fd-200d-1f3a8.png","sheet_x":18,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3A8","non_qualified":null,"image":"1f469-1f3fe-200d-1f3a8.png","sheet_x":18,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3A8","non_qualified":null,"image":"1f469-1f3ff-200d-1f3a8.png","sheet_x":18,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Artist","b":"1F469-200D-1F3A8","k":[18,41]},"flag-lr":{"a":"Liberia Flag","b":"1F1F1-1F1F7","k":[3,11]},"clock230":{"a":"Clock Face Two-Thirty","b":"1F55D","j":["time","late","early","schedule"],"k":[28,31]},"bow_and_arrow":{"a":"Bow and Arrow","b":"1F3F9","j":["sports"],"k":[12,23],"o":8},"male-pilot":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-2708-FE0F","non_qualified":"1F468-1F3FB-200D-2708","image":"1f468-1f3fb-200d-2708-fe0f.png","sheet_x":18,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-2708-FE0F","non_qualified":"1F468-1F3FC-200D-2708","image":"1f468-1f3fc-200d-2708-fe0f.png","sheet_x":18,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-2708-FE0F","non_qualified":"1F468-1F3FD-200D-2708","image":"1f468-1f3fd-200d-2708-fe0f.png","sheet_x":18,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-2708-FE0F","non_qualified":"1F468-1F3FE-200D-2708","image":"1f468-1f3fe-200d-2708-fe0f.png","sheet_x":18,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-2708-FE0F","non_qualified":"1F468-1F3FF-200D-2708","image":"1f468-1f3ff-200d-2708-fe0f.png","sheet_x":18,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Male Pilot","b":"1F468-200D-2708-FE0F","c":"1F468-200D-2708","k":[18,3]},"flag-ls":{"a":"Lesotho Flag","b":"1F1F1-1F1F8","k":[3,12]},"flag-lt":{"a":"Lithuania Flag","b":"1F1F1-1F1F9","k":[3,13]},"capital_abcd":{"a":"Input Symbol for Latin Capital Letters","b":"1F520","j":["alphabet","words","blue-square"],"k":[27,34]},"female-pilot":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-2708-FE0F","non_qualified":"1F469-1F3FB-200D-2708","image":"1f469-1f3fb-200d-2708-fe0f.png","sheet_x":20,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-2708-FE0F","non_qualified":"1F469-1F3FC-200D-2708","image":"1f469-1f3fc-200d-2708-fe0f.png","sheet_x":20,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-2708-FE0F","non_qualified":"1F469-1F3FD-200D-2708","image":"1f469-1f3fd-200d-2708-fe0f.png","sheet_x":20,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-2708-FE0F","non_qualified":"1F469-1F3FE-200D-2708","image":"1f469-1f3fe-200d-2708-fe0f.png","sheet_x":20,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-2708-FE0F","non_qualified":"1F469-1F3FF-200D-2708","image":"1f469-1f3ff-200d-2708-fe0f.png","sheet_x":20,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Female Pilot","b":"1F469-200D-2708-FE0F","c":"1F469-200D-2708","k":[20,13]},"clock3":{"a":"Clock Face Three Oclock","b":"1F552","j":["time","late","early","schedule"],"k":[28,20]},"shield":{"a":"Shield","b":"1F6E1-FE0F","c":"1F6E1","j":["protection","security"],"k":[37,9],"o":7},"male-astronaut":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F680","non_qualified":null,"image":"1f468-1f3fb-200d-1f680.png","sheet_x":17,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F680","non_qualified":null,"image":"1f468-1f3fc-200d-1f680.png","sheet_x":17,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F680","non_qualified":null,"image":"1f468-1f3fd-200d-1f680.png","sheet_x":17,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F680","non_qualified":null,"image":"1f468-1f3fe-200d-1f680.png","sheet_x":17,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F680","non_qualified":null,"image":"1f468-1f3ff-200d-1f680.png","sheet_x":17,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Astronaut","b":"1F468-200D-1F680","k":[17,31]},"abcd":{"a":"Input Symbol for Latin Small Letters","b":"1F521","j":["blue-square","alphabet"],"k":[27,35]},"clock330":{"a":"Clock Face Three-Thirty","b":"1F55E","j":["time","late","early","schedule"],"k":[28,32]},"flag-lu":{"a":"Luxembourg Flag","b":"1F1F1-1F1FA","k":[3,14]},"wrench":{"a":"Wrench","b":"1F527","j":["tools","diy","ikea","fix","maintainer"],"k":[27,41]},"nut_and_bolt":{"a":"Nut and Bolt","b":"1F529","j":["handy","tools","fix"],"k":[27,43]},"clock4":{"a":"Clock Face Four Oclock","b":"1F553","j":["time","late","early","schedule"],"k":[28,21]},"female-astronaut":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F680","non_qualified":null,"image":"1f469-1f3fb-200d-1f680.png","sheet_x":19,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F680","non_qualified":null,"image":"1f469-1f3fc-200d-1f680.png","sheet_x":19,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F680","non_qualified":null,"image":"1f469-1f3fd-200d-1f680.png","sheet_x":19,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F680","non_qualified":null,"image":"1f469-1f3fe-200d-1f680.png","sheet_x":19,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F680","non_qualified":null,"image":"1f469-1f3ff-200d-1f680.png","sheet_x":19,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Astronaut","b":"1F469-200D-1F680","k":[19,41]},"flag-lv":{"a":"Latvia Flag","b":"1F1F1-1F1FB","k":[3,15]},"gear":{"a":"Gear","b":"2699-FE0F","c":"2699","j":["cog"],"k":[48,17],"o":4},"male-firefighter":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F692","non_qualified":null,"image":"1f468-1f3fb-200d-1f692.png","sheet_x":17,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F692","non_qualified":null,"image":"1f468-1f3fc-200d-1f692.png","sheet_x":17,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F692","non_qualified":null,"image":"1f468-1f3fd-200d-1f692.png","sheet_x":17,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F692","non_qualified":null,"image":"1f468-1f3fe-200d-1f692.png","sheet_x":17,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F692","non_qualified":null,"image":"1f468-1f3ff-200d-1f692.png","sheet_x":17,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Firefighter","b":"1F468-200D-1F692","k":[17,37]},"flag-ly":{"a":"Libya Flag","b":"1F1F1-1F1FE","k":[3,16]},"symbols":{"a":"Input Symbol for Symbols","b":"1F523","j":["blue-square","music","note","ampersand","percent","glyphs","characters"],"k":[27,37]},"clock430":{"a":"Clock Face Four-Thirty","b":"1F55F","j":["time","late","early","schedule"],"k":[28,33]},"flag-ma":{"a":"Morocco Flag","b":"1F1F2-1F1E6","k":[3,17]},"compression":{"a":"Compression","b":"1F5DC-FE0F","c":"1F5DC","k":[30,10],"o":7},"female-firefighter":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F692","non_qualified":null,"image":"1f469-1f3fb-200d-1f692.png","sheet_x":19,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F692","non_qualified":null,"image":"1f469-1f3fc-200d-1f692.png","sheet_x":19,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F692","non_qualified":null,"image":"1f469-1f3fd-200d-1f692.png","sheet_x":19,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F692","non_qualified":null,"image":"1f469-1f3fe-200d-1f692.png","sheet_x":19,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F692","non_qualified":null,"image":"1f469-1f3ff-200d-1f692.png","sheet_x":20,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Firefighter","b":"1F469-200D-1F692","k":[19,47]},"abc":{"a":"Input Symbol for Latin Letters","b":"1F524","j":["blue-square","alphabet"],"k":[27,38]},"clock5":{"a":"Clock Face Five Oclock","b":"1F554","j":["time","late","early","schedule"],"k":[28,22]},"clock530":{"a":"Clock Face Five-Thirty","b":"1F560","j":["time","late","early","schedule"],"k":[28,34]},"a":{"a":"Negative Squared Latin Capital Letter a","b":"1F170-FE0F","c":"1F170","j":["red-square","alphabet","letter"],"k":[0,16]},"alembic":{"a":"Alembic","b":"2697-FE0F","c":"2697","j":["distilling","science","experiment","chemistry"],"k":[48,16],"o":4},"flag-mc":{"a":"Monaco Flag","b":"1F1F2-1F1E8","k":[3,18]},"cop":{"skin_variations":{"1F3FB":{"unified":"1F46E-1F3FB","non_qualified":null,"image":"1f46e-1f3fb.png","sheet_x":20,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F46E-1F3FC","non_qualified":null,"image":"1f46e-1f3fc.png","sheet_x":20,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F46E-1F3FD","non_qualified":null,"image":"1f46e-1f3fd.png","sheet_x":20,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F46E-1F3FE","non_qualified":null,"image":"1f46e-1f3fe.png","sheet_x":20,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F46E-1F3FF","non_qualified":null,"image":"1f46e-1f3ff.png","sheet_x":20,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F46E-200D-2642-FE0F","a":"Police Officer","b":"1F46E","k":[20,45]},"scales":{"a":"Scales","b":"2696-FE0F","c":"2696","k":[48,15],"o":4},"clock6":{"a":"Clock Face Six Oclock","b":"1F555","j":["time","late","early","schedule","dawn","dusk"],"k":[28,23]},"flag-md":{"a":"Moldova Flag","b":"1F1F2-1F1E9","k":[3,19]},"ab":{"a":"Negative Squared Ab","b":"1F18E","j":["red-square","alphabet"],"k":[0,20]},"male-police-officer":{"skin_variations":{"1F3FB":{"unified":"1F46E-1F3FB-200D-2642-FE0F","non_qualified":"1F46E-1F3FB-200D-2642","image":"1f46e-1f3fb-200d-2642-fe0f.png","sheet_x":20,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F46E-1F3FC-200D-2642-FE0F","non_qualified":"1F46E-1F3FC-200D-2642","image":"1f46e-1f3fc-200d-2642-fe0f.png","sheet_x":20,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F46E-1F3FD-200D-2642-FE0F","non_qualified":"1F46E-1F3FD-200D-2642","image":"1f46e-1f3fd-200d-2642-fe0f.png","sheet_x":20,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F46E-1F3FE-200D-2642-FE0F","non_qualified":"1F46E-1F3FE-200D-2642","image":"1f46e-1f3fe-200d-2642-fe0f.png","sheet_x":20,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F46E-1F3FF-200D-2642-FE0F","non_qualified":"1F46E-1F3FF-200D-2642","image":"1f46e-1f3ff-200d-2642-fe0f.png","sheet_x":20,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F46E","a":"Male Police Officer","b":"1F46E-200D-2642-FE0F","c":"1F46E-200D-2642","k":[20,39]},"link":{"a":"Link Symbol","b":"1F517","j":["rings","url"],"k":[27,25]},"flag-me":{"a":"Montenegro Flag","b":"1F1F2-1F1EA","k":[3,20]},"clock630":{"a":"Clock Face Six-Thirty","b":"1F561","j":["time","late","early","schedule"],"k":[28,35]},"b":{"a":"Negative Squared Latin Capital Letter B","b":"1F171-FE0F","c":"1F171","j":["red-square","alphabet","letter"],"k":[0,17]},"female-police-officer":{"skin_variations":{"1F3FB":{"unified":"1F46E-1F3FB-200D-2640-FE0F","non_qualified":"1F46E-1F3FB-200D-2640","image":"1f46e-1f3fb-200d-2640-fe0f.png","sheet_x":20,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F46E-1F3FC-200D-2640-FE0F","non_qualified":"1F46E-1F3FC-200D-2640","image":"1f46e-1f3fc-200d-2640-fe0f.png","sheet_x":20,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F46E-1F3FD-200D-2640-FE0F","non_qualified":"1F46E-1F3FD-200D-2640","image":"1f46e-1f3fd-200d-2640-fe0f.png","sheet_x":20,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F46E-1F3FE-200D-2640-FE0F","non_qualified":"1F46E-1F3FE-200D-2640","image":"1f46e-1f3fe-200d-2640-fe0f.png","sheet_x":20,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F46E-1F3FF-200D-2640-FE0F","non_qualified":"1F46E-1F3FF-200D-2640","image":"1f46e-1f3ff-200d-2640-fe0f.png","sheet_x":20,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Female Police Officer","b":"1F46E-200D-2640-FE0F","c":"1F46E-200D-2640","k":[20,33]},"clock7":{"a":"Clock Face Seven Oclock","b":"1F556","j":["time","late","early","schedule"],"k":[28,24]},"cl":{"a":"Squared Cl","b":"1F191","j":["alphabet","words","red-square"],"k":[0,21]},"sleuth_or_spy":{"skin_variations":{"1F3FB":{"unified":"1F575-1F3FB","non_qualified":null,"image":"1f575-1f3fb.png","sheet_x":29,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F575-1F3FC","non_qualified":null,"image":"1f575-1f3fc.png","sheet_x":29,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F575-1F3FD","non_qualified":null,"image":"1f575-1f3fd.png","sheet_x":29,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F575-1F3FE","non_qualified":null,"image":"1f575-1f3fe.png","sheet_x":29,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F575-1F3FF","non_qualified":null,"image":"1f575-1f3ff.png","sheet_x":29,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoleted_by":"1F575-FE0F-200D-2642-FE0F","a":"Sleuth or Spy","b":"1F575-FE0F","c":"1F575","k":[29,11],"o":7},"chains":{"a":"Chains","b":"26D3-FE0F","c":"26D3","j":["lock","arrest"],"k":[48,34],"o":5},"syringe":{"a":"Syringe","b":"1F489","j":["health","hospital","drugs","blood","medicine","needle","doctor","nurse"],"k":[24,35]},"male-detective":{"skin_variations":{"1F3FB":{"unified":"1F575-1F3FB-200D-2642-FE0F","non_qualified":"1F575-1F3FB-200D-2642","image":"1f575-1f3fb-200d-2642-fe0f.png","sheet_x":29,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F575-1F3FC-200D-2642-FE0F","non_qualified":"1F575-1F3FC-200D-2642","image":"1f575-1f3fc-200d-2642-fe0f.png","sheet_x":29,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F575-1F3FD-200D-2642-FE0F","non_qualified":"1F575-1F3FD-200D-2642","image":"1f575-1f3fd-200d-2642-fe0f.png","sheet_x":29,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F575-1F3FE-200D-2642-FE0F","non_qualified":"1F575-1F3FE-200D-2642","image":"1f575-1f3fe-200d-2642-fe0f.png","sheet_x":29,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F575-1F3FF-200D-2642-FE0F","non_qualified":"1F575-1F3FF-200D-2642","image":"1f575-1f3ff-200d-2642-fe0f.png","sheet_x":29,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F575-FE0F","a":"Male Detective","b":"1F575-FE0F-200D-2642-FE0F","k":[29,5],"o":7},"cool":{"a":"Squared Cool","b":"1F192","j":["words","blue-square"],"k":[0,22]},"clock730":{"a":"Clock Face Seven-Thirty","b":"1F562","j":["time","late","early","schedule"],"k":[28,36]},"flag-mg":{"a":"Madagascar Flag","b":"1F1F2-1F1EC","k":[3,22]},"free":{"a":"Squared Free","b":"1F193","j":["blue-square","words"],"k":[0,23]},"flag-mh":{"a":"Marshall Islands Flag","b":"1F1F2-1F1ED","k":[3,23]},"clock8":{"a":"Clock Face Eight Oclock","b":"1F557","j":["time","late","early","schedule"],"k":[28,25]},"pill":{"a":"Pill","b":"1F48A","j":["health","medicine","doctor","pharmacy","drug"],"k":[24,36]},"female-detective":{"skin_variations":{"1F3FB":{"unified":"1F575-1F3FB-200D-2640-FE0F","non_qualified":"1F575-1F3FB-200D-2640","image":"1f575-1f3fb-200d-2640-fe0f.png","sheet_x":29,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F575-1F3FC-200D-2640-FE0F","non_qualified":"1F575-1F3FC-200D-2640","image":"1f575-1f3fc-200d-2640-fe0f.png","sheet_x":29,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F575-1F3FD-200D-2640-FE0F","non_qualified":"1F575-1F3FD-200D-2640","image":"1f575-1f3fd-200d-2640-fe0f.png","sheet_x":29,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F575-1F3FE-200D-2640-FE0F","non_qualified":"1F575-1F3FE-200D-2640","image":"1f575-1f3fe-200d-2640-fe0f.png","sheet_x":29,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F575-1F3FF-200D-2640-FE0F","non_qualified":"1F575-1F3FF-200D-2640","image":"1f575-1f3ff-200d-2640-fe0f.png","sheet_x":29,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Female Detective","b":"1F575-FE0F-200D-2640-FE0F","k":[28,51],"o":7},"clock830":{"a":"Clock Face Eight-Thirty","b":"1F563","j":["time","late","early","schedule"],"k":[28,37]},"guardsman":{"skin_variations":{"1F3FB":{"unified":"1F482-1F3FB","non_qualified":null,"image":"1f482-1f3fb.png","sheet_x":23,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F482-1F3FC","non_qualified":null,"image":"1f482-1f3fc.png","sheet_x":23,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F482-1F3FD","non_qualified":null,"image":"1f482-1f3fd.png","sheet_x":23,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F482-1F3FE","non_qualified":null,"image":"1f482-1f3fe.png","sheet_x":23,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F482-1F3FF","non_qualified":null,"image":"1f482-1f3ff.png","sheet_x":23,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F482-200D-2642-FE0F","a":"Guardsman","b":"1F482","j":["uk","gb","british","male","guy","royal"],"k":[23,31]},"information_source":{"a":"Information Source","b":"2139-FE0F","c":"2139","j":["blue-square","alphabet","letter"],"k":[46,32],"o":3},"flag-mk":{"a":"Macedonia Flag","b":"1F1F2-1F1F0","k":[3,24]},"smoking":{"a":"Smoking Symbol","b":"1F6AC","j":["kills","tobacco","cigarette","joint","smoke"],"k":[35,17]},"id":{"a":"Squared Id","b":"1F194","j":["purple-square","words"],"k":[0,24]},"clock9":{"a":"Clock Face Nine Oclock","b":"1F558","j":["time","late","early","schedule"],"k":[28,26]},"flag-ml":{"a":"Mali Flag","b":"1F1F2-1F1F1","k":[3,25]},"coffin":{"a":"Coffin","b":"26B0-FE0F","c":"26B0","j":["vampire","dead","die","death","rip","graveyard","cemetery","casket","funeral","box"],"k":[48,24],"o":4},"male-guard":{"skin_variations":{"1F3FB":{"unified":"1F482-1F3FB-200D-2642-FE0F","non_qualified":"1F482-1F3FB-200D-2642","image":"1f482-1f3fb-200d-2642-fe0f.png","sheet_x":23,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F482-1F3FC-200D-2642-FE0F","non_qualified":"1F482-1F3FC-200D-2642","image":"1f482-1f3fc-200d-2642-fe0f.png","sheet_x":23,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F482-1F3FD-200D-2642-FE0F","non_qualified":"1F482-1F3FD-200D-2642","image":"1f482-1f3fd-200d-2642-fe0f.png","sheet_x":23,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F482-1F3FE-200D-2642-FE0F","non_qualified":"1F482-1F3FE-200D-2642","image":"1f482-1f3fe-200d-2642-fe0f.png","sheet_x":23,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F482-1F3FF-200D-2642-FE0F","non_qualified":"1F482-1F3FF-200D-2642","image":"1f482-1f3ff-200d-2642-fe0f.png","sheet_x":23,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F482","a":"Male Guard","b":"1F482-200D-2642-FE0F","c":"1F482-200D-2642","k":[23,25]},"m":{"a":"Circled Latin Capital Letter M","b":"24C2-FE0F","c":"24C2","j":["alphabet","blue-circle","letter"],"k":[47,7],"o":1},"funeral_urn":{"a":"Funeral Urn","b":"26B1-FE0F","c":"26B1","j":["dead","die","death","rip","ashes"],"k":[48,25],"o":4},"female-guard":{"skin_variations":{"1F3FB":{"unified":"1F482-1F3FB-200D-2640-FE0F","non_qualified":"1F482-1F3FB-200D-2640","image":"1f482-1f3fb-200d-2640-fe0f.png","sheet_x":23,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F482-1F3FC-200D-2640-FE0F","non_qualified":"1F482-1F3FC-200D-2640","image":"1f482-1f3fc-200d-2640-fe0f.png","sheet_x":23,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F482-1F3FD-200D-2640-FE0F","non_qualified":"1F482-1F3FD-200D-2640","image":"1f482-1f3fd-200d-2640-fe0f.png","sheet_x":23,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F482-1F3FE-200D-2640-FE0F","non_qualified":"1F482-1F3FE-200D-2640","image":"1f482-1f3fe-200d-2640-fe0f.png","sheet_x":23,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F482-1F3FF-200D-2640-FE0F","non_qualified":"1F482-1F3FF-200D-2640","image":"1f482-1f3ff-200d-2640-fe0f.png","sheet_x":23,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Female Guard","b":"1F482-200D-2640-FE0F","c":"1F482-200D-2640","k":[23,19]},"flag-mm":{"a":"Myanmar (burma) Flag","b":"1F1F2-1F1F2","k":[3,26]},"clock930":{"a":"Clock Face Nine-Thirty","b":"1F564","j":["time","late","early","schedule"],"k":[28,38]},"moyai":{"a":"Moyai","b":"1F5FF","j":["rock","easter island","moai"],"k":[30,23]},"new":{"a":"Squared New","b":"1F195","j":["blue-square","words","start"],"k":[0,25]},"flag-mn":{"a":"Mongolia Flag","b":"1F1F2-1F1F3","k":[3,27]},"construction_worker":{"skin_variations":{"1F3FB":{"unified":"1F477-1F3FB","non_qualified":null,"image":"1f477-1f3fb.png","sheet_x":22,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F477-1F3FC","non_qualified":null,"image":"1f477-1f3fc.png","sheet_x":22,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F477-1F3FD","non_qualified":null,"image":"1f477-1f3fd.png","sheet_x":22,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F477-1F3FE","non_qualified":null,"image":"1f477-1f3fe.png","sheet_x":22,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F477-1F3FF","non_qualified":null,"image":"1f477-1f3ff.png","sheet_x":22,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F477-200D-2642-FE0F","a":"Construction Worker","b":"1F477","k":[22,28]},"clock10":{"a":"Clock Face Ten Oclock","b":"1F559","j":["time","late","early","schedule"],"k":[28,27]},"clock1030":{"a":"Clock Face Ten-Thirty","b":"1F565","j":["time","late","early","schedule"],"k":[28,39]},"ng":{"a":"Squared Ng","b":"1F196","j":["blue-square","words","shape","icon"],"k":[0,26]},"male-construction-worker":{"skin_variations":{"1F3FB":{"unified":"1F477-1F3FB-200D-2642-FE0F","non_qualified":"1F477-1F3FB-200D-2642","image":"1f477-1f3fb-200d-2642-fe0f.png","sheet_x":22,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F477-1F3FC-200D-2642-FE0F","non_qualified":"1F477-1F3FC-200D-2642","image":"1f477-1f3fc-200d-2642-fe0f.png","sheet_x":22,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F477-1F3FD-200D-2642-FE0F","non_qualified":"1F477-1F3FD-200D-2642","image":"1f477-1f3fd-200d-2642-fe0f.png","sheet_x":22,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F477-1F3FE-200D-2642-FE0F","non_qualified":"1F477-1F3FE-200D-2642","image":"1f477-1f3fe-200d-2642-fe0f.png","sheet_x":22,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F477-1F3FF-200D-2642-FE0F","non_qualified":"1F477-1F3FF-200D-2642","image":"1f477-1f3ff-200d-2642-fe0f.png","sheet_x":22,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F477","a":"Male Construction Worker","b":"1F477-200D-2642-FE0F","c":"1F477-200D-2642","k":[22,22]},"flag-mo":{"a":"Macau Sar China Flag","b":"1F1F2-1F1F4","k":[3,28]},"oil_drum":{"a":"Oil Drum","b":"1F6E2-FE0F","c":"1F6E2","j":["barrell"],"k":[37,10],"o":7},"o2":{"a":"Negative Squared Latin Capital Letter O","b":"1F17E-FE0F","c":"1F17E","j":["alphabet","red-square","letter"],"k":[0,18]},"female-construction-worker":{"skin_variations":{"1F3FB":{"unified":"1F477-1F3FB-200D-2640-FE0F","non_qualified":"1F477-1F3FB-200D-2640","image":"1f477-1f3fb-200d-2640-fe0f.png","sheet_x":22,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F477-1F3FC-200D-2640-FE0F","non_qualified":"1F477-1F3FC-200D-2640","image":"1f477-1f3fc-200d-2640-fe0f.png","sheet_x":22,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F477-1F3FD-200D-2640-FE0F","non_qualified":"1F477-1F3FD-200D-2640","image":"1f477-1f3fd-200d-2640-fe0f.png","sheet_x":22,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F477-1F3FE-200D-2640-FE0F","non_qualified":"1F477-1F3FE-200D-2640","image":"1f477-1f3fe-200d-2640-fe0f.png","sheet_x":22,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F477-1F3FF-200D-2640-FE0F","non_qualified":"1F477-1F3FF-200D-2640","image":"1f477-1f3ff-200d-2640-fe0f.png","sheet_x":22,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Female Construction Worker","b":"1F477-200D-2640-FE0F","c":"1F477-200D-2640","k":[22,16]},"clock11":{"a":"Clock Face Eleven Oclock","b":"1F55A","j":["time","late","early","schedule"],"k":[28,28]},"crystal_ball":{"a":"Crystal Ball","b":"1F52E","j":["disco","party","magic","circus","fortune_teller"],"k":[27,48]},"flag-mp":{"a":"Northern Mariana Islands Flag","b":"1F1F2-1F1F5","k":[3,29]},"prince":{"skin_variations":{"1F3FB":{"unified":"1F934-1F3FB","non_qualified":null,"image":"1f934-1f3fb.png","sheet_x":39,"sheet_y":29,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F934-1F3FC","non_qualified":null,"image":"1f934-1f3fc.png","sheet_x":39,"sheet_y":30,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F934-1F3FD","non_qualified":null,"image":"1f934-1f3fd.png","sheet_x":39,"sheet_y":31,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F934-1F3FE","non_qualified":null,"image":"1f934-1f3fe.png","sheet_x":39,"sheet_y":32,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F934-1F3FF","non_qualified":null,"image":"1f934-1f3ff.png","sheet_x":39,"sheet_y":33,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Prince","b":"1F934","j":["boy","man","male","crown","royal","king"],"k":[39,28],"o":9},"ok":{"a":"Squared Ok","b":"1F197","j":["good","agree","yes","blue-square"],"k":[0,27]},"clock1130":{"a":"Clock Face Eleven-Thirty","b":"1F566","j":["time","late","early","schedule"],"k":[28,40]},"shopping_trolley":{"a":"Shopping Trolley","b":"1F6D2","k":[37,7],"o":9},"flag-mr":{"a":"Mauritania Flag","b":"1F1F2-1F1F7","k":[3,31]},"princess":{"skin_variations":{"1F3FB":{"unified":"1F478-1F3FB","non_qualified":null,"image":"1f478-1f3fb.png","sheet_x":22,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F478-1F3FC","non_qualified":null,"image":"1f478-1f3fc.png","sheet_x":22,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F478-1F3FD","non_qualified":null,"image":"1f478-1f3fd.png","sheet_x":22,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F478-1F3FE","non_qualified":null,"image":"1f478-1f3fe.png","sheet_x":22,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F478-1F3FF","non_qualified":null,"image":"1f478-1f3ff.png","sheet_x":22,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Princess","b":"1F478","j":["girl","woman","female","blond","crown","royal","queen"],"k":[22,34]},"new_moon":{"a":"New Moon Symbol","b":"1F311","j":["nature","twilight","planet","space","night","evening","sleep"],"k":[6,9]},"parking":{"a":"Negative Squared Latin Capital Letter P","b":"1F17F-FE0F","c":"1F17F","j":["cars","blue-square","alphabet","letter"],"k":[0,19],"o":5},"sos":{"a":"Squared Sos","b":"1F198","j":["help","red-square","words","emergency","911"],"k":[0,28]},"man_with_turban":{"skin_variations":{"1F3FB":{"unified":"1F473-1F3FB","non_qualified":null,"image":"1f473-1f3fb.png","sheet_x":21,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F473-1F3FC","non_qualified":null,"image":"1f473-1f3fc.png","sheet_x":21,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F473-1F3FD","non_qualified":null,"image":"1f473-1f3fd.png","sheet_x":21,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F473-1F3FE","non_qualified":null,"image":"1f473-1f3fe.png","sheet_x":21,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F473-1F3FF","non_qualified":null,"image":"1f473-1f3ff.png","sheet_x":21,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F473-200D-2642-FE0F","a":"Man with Turban","b":"1F473","j":["male","indian","hinduism","arabs"],"k":[21,44]},"flag-ms":{"a":"Montserrat Flag","b":"1F1F2-1F1F8","k":[3,32]},"waxing_crescent_moon":{"a":"Waxing Crescent Moon Symbol","b":"1F312","j":["nature","twilight","planet","space","night","evening","sleep"],"k":[6,10]},"up":{"a":"Squared Up with Exclamation Mark","b":"1F199","j":["blue-square","above","high"],"k":[0,29]},"first_quarter_moon":{"a":"First Quarter Moon Symbol","b":"1F313","j":["nature","twilight","planet","space","night","evening","sleep"],"k":[6,11]},"flag-mt":{"a":"Malta Flag","b":"1F1F2-1F1F9","k":[3,33]},"man-wearing-turban":{"skin_variations":{"1F3FB":{"unified":"1F473-1F3FB-200D-2642-FE0F","non_qualified":"1F473-1F3FB-200D-2642","image":"1f473-1f3fb-200d-2642-fe0f.png","sheet_x":21,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F473-1F3FC-200D-2642-FE0F","non_qualified":"1F473-1F3FC-200D-2642","image":"1f473-1f3fc-200d-2642-fe0f.png","sheet_x":21,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F473-1F3FD-200D-2642-FE0F","non_qualified":"1F473-1F3FD-200D-2642","image":"1f473-1f3fd-200d-2642-fe0f.png","sheet_x":21,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F473-1F3FE-200D-2642-FE0F","non_qualified":"1F473-1F3FE-200D-2642","image":"1f473-1f3fe-200d-2642-fe0f.png","sheet_x":21,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F473-1F3FF-200D-2642-FE0F","non_qualified":"1F473-1F3FF-200D-2642","image":"1f473-1f3ff-200d-2642-fe0f.png","sheet_x":21,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F473","a":"Man Wearing Turban","b":"1F473-200D-2642-FE0F","c":"1F473-200D-2642","k":[21,38]},"moon":{"a":"Waxing Gibbous Moon Symbol","b":"1F314","k":[6,12],"n":["waxing_gibbous_moon"]},"woman-wearing-turban":{"skin_variations":{"1F3FB":{"unified":"1F473-1F3FB-200D-2640-FE0F","non_qualified":"1F473-1F3FB-200D-2640","image":"1f473-1f3fb-200d-2640-fe0f.png","sheet_x":21,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F473-1F3FC-200D-2640-FE0F","non_qualified":"1F473-1F3FC-200D-2640","image":"1f473-1f3fc-200d-2640-fe0f.png","sheet_x":21,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F473-1F3FD-200D-2640-FE0F","non_qualified":"1F473-1F3FD-200D-2640","image":"1f473-1f3fd-200d-2640-fe0f.png","sheet_x":21,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F473-1F3FE-200D-2640-FE0F","non_qualified":"1F473-1F3FE-200D-2640","image":"1f473-1f3fe-200d-2640-fe0f.png","sheet_x":21,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F473-1F3FF-200D-2640-FE0F","non_qualified":"1F473-1F3FF-200D-2640","image":"1f473-1f3ff-200d-2640-fe0f.png","sheet_x":21,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Wearing Turban","b":"1F473-200D-2640-FE0F","c":"1F473-200D-2640","k":[21,32]},"vs":{"a":"Squared Vs","b":"1F19A","j":["words","orange-square"],"k":[0,30]},"flag-mu":{"a":"Mauritius Flag","b":"1F1F2-1F1FA","k":[3,34]},"man_with_gua_pi_mao":{"skin_variations":{"1F3FB":{"unified":"1F472-1F3FB","non_qualified":null,"image":"1f472-1f3fb.png","sheet_x":21,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F472-1F3FC","non_qualified":null,"image":"1f472-1f3fc.png","sheet_x":21,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F472-1F3FD","non_qualified":null,"image":"1f472-1f3fd.png","sheet_x":21,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F472-1F3FE","non_qualified":null,"image":"1f472-1f3fe.png","sheet_x":21,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F472-1F3FF","non_qualified":null,"image":"1f472-1f3ff.png","sheet_x":21,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Man with Gua Pi Mao","b":"1F472","j":["male","boy","chinese"],"k":[21,26]},"koko":{"a":"Squared Katakana Koko","b":"1F201","j":["blue-square","here","katakana","japanese","destination"],"k":[5,29]},"full_moon":{"a":"Full Moon Symbol","b":"1F315","j":["nature","yellow","twilight","planet","space","night","evening","sleep"],"k":[6,13]},"flag-mv":{"a":"Maldives Flag","b":"1F1F2-1F1FB","k":[3,35]},"person_with_headscarf":{"skin_variations":{"1F3FB":{"unified":"1F9D5-1F3FB","non_qualified":null,"image":"1f9d5-1f3fb.png","sheet_x":43,"sheet_y":23,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F9D5-1F3FC","non_qualified":null,"image":"1f9d5-1f3fc.png","sheet_x":43,"sheet_y":24,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F9D5-1F3FD","non_qualified":null,"image":"1f9d5-1f3fd.png","sheet_x":43,"sheet_y":25,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F9D5-1F3FE","non_qualified":null,"image":"1f9d5-1f3fe.png","sheet_x":43,"sheet_y":26,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F9D5-1F3FF","non_qualified":null,"image":"1f9d5-1f3ff.png","sheet_x":43,"sheet_y":27,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Person with Headscarf","b":"1F9D5","k":[43,22],"o":10},"waning_gibbous_moon":{"a":"Waning Gibbous Moon Symbol","b":"1F316","j":["nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"],"k":[6,14]},"sa":{"a":"Squared Katakana Sa","b":"1F202-FE0F","c":"1F202","j":["japanese","blue-square","katakana"],"k":[5,30]},"flag-mw":{"a":"Malawi Flag","b":"1F1F2-1F1FC","k":[3,36]},"last_quarter_moon":{"a":"Last Quarter Moon Symbol","b":"1F317","j":["nature","twilight","planet","space","night","evening","sleep"],"k":[6,15]},"u6708":{"a":"Squared Cjk Unified Ideograph-6708","b":"1F237-FE0F","c":"1F237","j":["chinese","month","moon","japanese","orange-square","kanji"],"k":[5,38]},"bearded_person":{"skin_variations":{"1F3FB":{"unified":"1F9D4-1F3FB","non_qualified":null,"image":"1f9d4-1f3fb.png","sheet_x":43,"sheet_y":17,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F9D4-1F3FC","non_qualified":null,"image":"1f9d4-1f3fc.png","sheet_x":43,"sheet_y":18,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F9D4-1F3FD","non_qualified":null,"image":"1f9d4-1f3fd.png","sheet_x":43,"sheet_y":19,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F9D4-1F3FE","non_qualified":null,"image":"1f9d4-1f3fe.png","sheet_x":43,"sheet_y":20,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F9D4-1F3FF","non_qualified":null,"image":"1f9d4-1f3ff.png","sheet_x":43,"sheet_y":21,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Bearded Person","b":"1F9D4","k":[43,16],"o":10},"flag-mx":{"a":"Mexico Flag","b":"1F1F2-1F1FD","k":[3,37]},"u6709":{"a":"Squared Cjk Unified Ideograph-6709","b":"1F236","j":["orange-square","chinese","have","kanji"],"k":[5,37]},"person_with_blond_hair":{"skin_variations":{"1F3FB":{"unified":"1F471-1F3FB","non_qualified":null,"image":"1f471-1f3fb.png","sheet_x":21,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F471-1F3FC","non_qualified":null,"image":"1f471-1f3fc.png","sheet_x":21,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F471-1F3FD","non_qualified":null,"image":"1f471-1f3fd.png","sheet_x":21,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F471-1F3FE","non_qualified":null,"image":"1f471-1f3fe.png","sheet_x":21,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F471-1F3FF","non_qualified":null,"image":"1f471-1f3ff.png","sheet_x":21,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F471-200D-2642-FE0F","a":"Person with Blond Hair","b":"1F471","k":[21,20]},"waning_crescent_moon":{"a":"Waning Crescent Moon Symbol","b":"1F318","j":["nature","twilight","planet","space","night","evening","sleep"],"k":[6,16]},"flag-my":{"a":"Malaysia Flag","b":"1F1F2-1F1FE","k":[3,38]},"u6307":{"a":"Squared Cjk Unified Ideograph-6307","b":"1F22F","j":["chinese","point","green-square","kanji"],"k":[5,32],"o":5},"blond-haired-man":{"skin_variations":{"1F3FB":{"unified":"1F471-1F3FB-200D-2642-FE0F","non_qualified":"1F471-1F3FB-200D-2642","image":"1f471-1f3fb-200d-2642-fe0f.png","sheet_x":21,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F471-1F3FC-200D-2642-FE0F","non_qualified":"1F471-1F3FC-200D-2642","image":"1f471-1f3fc-200d-2642-fe0f.png","sheet_x":21,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F471-1F3FD-200D-2642-FE0F","non_qualified":"1F471-1F3FD-200D-2642","image":"1f471-1f3fd-200d-2642-fe0f.png","sheet_x":21,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F471-1F3FE-200D-2642-FE0F","non_qualified":"1F471-1F3FE-200D-2642","image":"1f471-1f3fe-200d-2642-fe0f.png","sheet_x":21,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F471-1F3FF-200D-2642-FE0F","non_qualified":"1F471-1F3FF-200D-2642","image":"1f471-1f3ff-200d-2642-fe0f.png","sheet_x":21,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F471","a":"Blond Haired Man","b":"1F471-200D-2642-FE0F","c":"1F471-200D-2642","k":[21,14]},"crescent_moon":{"a":"Crescent Moon","b":"1F319","j":["night","sleep","sky","evening","magic"],"k":[6,17]},"flag-mz":{"a":"Mozambique Flag","b":"1F1F2-1F1FF","k":[3,39]},"new_moon_with_face":{"a":"New Moon with Face","b":"1F31A","j":["nature","twilight","planet","space","night","evening","sleep"],"k":[6,18]},"flag-na":{"a":"Namibia Flag","b":"1F1F3-1F1E6","k":[3,40]},"blond-haired-woman":{"skin_variations":{"1F3FB":{"unified":"1F471-1F3FB-200D-2640-FE0F","non_qualified":"1F471-1F3FB-200D-2640","image":"1f471-1f3fb-200d-2640-fe0f.png","sheet_x":21,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F471-1F3FC-200D-2640-FE0F","non_qualified":"1F471-1F3FC-200D-2640","image":"1f471-1f3fc-200d-2640-fe0f.png","sheet_x":21,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F471-1F3FD-200D-2640-FE0F","non_qualified":"1F471-1F3FD-200D-2640","image":"1f471-1f3fd-200d-2640-fe0f.png","sheet_x":21,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F471-1F3FE-200D-2640-FE0F","non_qualified":"1F471-1F3FE-200D-2640","image":"1f471-1f3fe-200d-2640-fe0f.png","sheet_x":21,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F471-1F3FF-200D-2640-FE0F","non_qualified":"1F471-1F3FF-200D-2640","image":"1f471-1f3ff-200d-2640-fe0f.png","sheet_x":21,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Blond Haired Woman","b":"1F471-200D-2640-FE0F","c":"1F471-200D-2640","k":[21,8]},"ideograph_advantage":{"a":"Circled Ideograph Advantage","b":"1F250","j":["chinese","kanji","obtain","get","circle"],"k":[5,42]},"first_quarter_moon_with_face":{"a":"First Quarter Moon with Face","b":"1F31B","j":["nature","twilight","planet","space","night","evening","sleep"],"k":[6,19]},"man_in_tuxedo":{"skin_variations":{"1F3FB":{"unified":"1F935-1F3FB","non_qualified":null,"image":"1f935-1f3fb.png","sheet_x":39,"sheet_y":35,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F935-1F3FC","non_qualified":null,"image":"1f935-1f3fc.png","sheet_x":39,"sheet_y":36,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F935-1F3FD","non_qualified":null,"image":"1f935-1f3fd.png","sheet_x":39,"sheet_y":37,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F935-1F3FE","non_qualified":null,"image":"1f935-1f3fe.png","sheet_x":39,"sheet_y":38,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F935-1F3FF","non_qualified":null,"image":"1f935-1f3ff.png","sheet_x":39,"sheet_y":39,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Man in Tuxedo","b":"1F935","j":["couple","marriage","wedding","groom"],"k":[39,34],"o":9},"u5272":{"a":"Squared Cjk Unified Ideograph-5272","b":"1F239","j":["cut","divide","chinese","kanji","pink-square"],"k":[5,40]},"flag-ne":{"a":"Niger Flag","b":"1F1F3-1F1EA","k":[3,42]},"last_quarter_moon_with_face":{"a":"Last Quarter Moon with Face","b":"1F31C","j":["nature","twilight","planet","space","night","evening","sleep"],"k":[6,20]},"u7121":{"a":"Squared Cjk Unified Ideograph-7121","b":"1F21A","j":["nothing","chinese","kanji","japanese","orange-square"],"k":[5,31],"o":5},"bride_with_veil":{"skin_variations":{"1F3FB":{"unified":"1F470-1F3FB","non_qualified":null,"image":"1f470-1f3fb.png","sheet_x":21,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F470-1F3FC","non_qualified":null,"image":"1f470-1f3fc.png","sheet_x":21,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F470-1F3FD","non_qualified":null,"image":"1f470-1f3fd.png","sheet_x":21,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F470-1F3FE","non_qualified":null,"image":"1f470-1f3fe.png","sheet_x":21,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F470-1F3FF","non_qualified":null,"image":"1f470-1f3ff.png","sheet_x":21,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Bride with Veil","b":"1F470","j":["couple","marriage","wedding","woman","bride"],"k":[21,2]},"u7981":{"a":"Squared Cjk Unified Ideograph-7981","b":"1F232","j":["kanji","japanese","chinese","forbidden","limit","restricted","red-square"],"k":[5,33]},"pregnant_woman":{"skin_variations":{"1F3FB":{"unified":"1F930-1F3FB","non_qualified":null,"image":"1f930-1f3fb.png","sheet_x":39,"sheet_y":5,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F930-1F3FC","non_qualified":null,"image":"1f930-1f3fc.png","sheet_x":39,"sheet_y":6,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F930-1F3FD","non_qualified":null,"image":"1f930-1f3fd.png","sheet_x":39,"sheet_y":7,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F930-1F3FE","non_qualified":null,"image":"1f930-1f3fe.png","sheet_x":39,"sheet_y":8,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F930-1F3FF","non_qualified":null,"image":"1f930-1f3ff.png","sheet_x":39,"sheet_y":9,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Pregnant Woman","b":"1F930","j":["baby"],"k":[39,4],"o":9},"thermometer":{"a":"Thermometer","b":"1F321-FE0F","c":"1F321","j":["weather","temperature","hot","cold"],"k":[6,25],"o":7},"flag-nf":{"a":"Norfolk Island Flag","b":"1F1F3-1F1EB","k":[3,43]},"sunny":{"a":"Black Sun with Rays","b":"2600-FE0F","c":"2600","j":["weather","nature","brightness","summer","beach","spring"],"k":[47,16],"o":1},"accept":{"a":"Circled Ideograph Accept","b":"1F251","j":["ok","good","chinese","kanji","agree","yes","orange-circle"],"k":[5,43]},"flag-ng":{"a":"Nigeria Flag","b":"1F1F3-1F1EC","k":[3,44]},"breast-feeding":{"skin_variations":{"1F3FB":{"unified":"1F931-1F3FB","non_qualified":null,"image":"1f931-1f3fb.png","sheet_x":39,"sheet_y":11,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F931-1F3FC","non_qualified":null,"image":"1f931-1f3fc.png","sheet_x":39,"sheet_y":12,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F931-1F3FD","non_qualified":null,"image":"1f931-1f3fd.png","sheet_x":39,"sheet_y":13,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F931-1F3FE","non_qualified":null,"image":"1f931-1f3fe.png","sheet_x":39,"sheet_y":14,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F931-1F3FF","non_qualified":null,"image":"1f931-1f3ff.png","sheet_x":39,"sheet_y":15,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Breast-Feeding","b":"1F931","k":[39,10],"o":10},"full_moon_with_face":{"a":"Full Moon with Face","b":"1F31D","j":["nature","twilight","planet","space","night","evening","sleep"],"k":[6,21]},"flag-ni":{"a":"Nicaragua Flag","b":"1F1F3-1F1EE","k":[3,45]},"u7533":{"a":"Squared Cjk Unified Ideograph-7533","b":"1F238","j":["chinese","japanese","kanji","orange-square"],"k":[5,39]},"angel":{"skin_variations":{"1F3FB":{"unified":"1F47C-1F3FB","non_qualified":null,"image":"1f47c-1f3fb.png","sheet_x":22,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F47C-1F3FC","non_qualified":null,"image":"1f47c-1f3fc.png","sheet_x":22,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F47C-1F3FD","non_qualified":null,"image":"1f47c-1f3fd.png","sheet_x":22,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F47C-1F3FE","non_qualified":null,"image":"1f47c-1f3fe.png","sheet_x":22,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F47C-1F3FF","non_qualified":null,"image":"1f47c-1f3ff.png","sheet_x":22,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Baby Angel","b":"1F47C","j":["heaven","wings","halo"],"k":[22,43]},"sun_with_face":{"a":"Sun with Face","b":"1F31E","j":["nature","morning","sky"],"k":[6,22]},"santa":{"skin_variations":{"1F3FB":{"unified":"1F385-1F3FB","non_qualified":null,"image":"1f385-1f3fb.png","sheet_x":8,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F385-1F3FC","non_qualified":null,"image":"1f385-1f3fc.png","sheet_x":8,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F385-1F3FD","non_qualified":null,"image":"1f385-1f3fd.png","sheet_x":8,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F385-1F3FE","non_qualified":null,"image":"1f385-1f3fe.png","sheet_x":8,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F385-1F3FF","non_qualified":null,"image":"1f385-1f3ff.png","sheet_x":8,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Father Christmas","b":"1F385","j":["festival","man","male","xmas","father christmas"],"k":[8,19]},"u5408":{"a":"Squared Cjk Unified Ideograph-5408","b":"1F234","j":["japanese","chinese","join","kanji","red-square"],"k":[5,35]},"flag-nl":{"a":"Netherlands Flag","b":"1F1F3-1F1F1","k":[3,46]},"mrs_claus":{"skin_variations":{"1F3FB":{"unified":"1F936-1F3FB","non_qualified":null,"image":"1f936-1f3fb.png","sheet_x":39,"sheet_y":41,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F936-1F3FC","non_qualified":null,"image":"1f936-1f3fc.png","sheet_x":39,"sheet_y":42,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F936-1F3FD","non_qualified":null,"image":"1f936-1f3fd.png","sheet_x":39,"sheet_y":43,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F936-1F3FE","non_qualified":null,"image":"1f936-1f3fe.png","sheet_x":39,"sheet_y":44,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F936-1F3FF","non_qualified":null,"image":"1f936-1f3ff.png","sheet_x":39,"sheet_y":45,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Mother Christmas","b":"1F936","j":["woman","female","xmas","mother christmas"],"k":[39,40],"n":["mother_christmas"],"o":9},"u7a7a":{"a":"Squared Cjk Unified Ideograph-7a7a","b":"1F233","j":["kanji","japanese","chinese","empty","sky","blue-square"],"k":[5,34]},"star":{"a":"White Medium Star","b":"2B50","j":["night","yellow"],"k":[50,22],"o":5},"flag-no":{"a":"Norway Flag","b":"1F1F3-1F1F4","k":[3,47]},"mage":{"skin_variations":{"1F3FB":{"unified":"1F9D9-1F3FB","non_qualified":null,"image":"1f9d9-1f3fb.png","sheet_x":44,"sheet_y":43,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D9-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9D9-1F3FC","non_qualified":null,"image":"1f9d9-1f3fc.png","sheet_x":44,"sheet_y":44,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D9-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9D9-1F3FD","non_qualified":null,"image":"1f9d9-1f3fd.png","sheet_x":44,"sheet_y":45,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D9-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9D9-1F3FE","non_qualified":null,"image":"1f9d9-1f3fe.png","sheet_x":44,"sheet_y":46,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D9-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9D9-1F3FF","non_qualified":null,"image":"1f9d9-1f3ff.png","sheet_x":44,"sheet_y":47,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D9-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9D9-200D-2640-FE0F","a":"Mage","b":"1F9D9","k":[44,42],"o":10},"star2":{"a":"Glowing Star","b":"1F31F","j":["night","sparkle","awesome","good","magic"],"k":[6,23]},"flag-np":{"a":"Nepal Flag","b":"1F1F3-1F1F5","k":[3,48]},"congratulations":{"a":"Circled Ideograph Congratulation","b":"3297-FE0F","c":"3297","j":["chinese","kanji","japanese","red-circle"],"k":[50,26],"o":1},"flag-nr":{"a":"Nauru Flag","b":"1F1F3-1F1F7","k":[3,49]},"stars":{"a":"Shooting Star","b":"1F320","j":["night","photo"],"k":[6,24]},"female_mage":{"skin_variations":{"1F3FB":{"unified":"1F9D9-1F3FB-200D-2640-FE0F","non_qualified":"1F9D9-1F3FB-200D-2640","image":"1f9d9-1f3fb-200d-2640-fe0f.png","sheet_x":44,"sheet_y":31,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D9-1F3FB"},"1F3FC":{"unified":"1F9D9-1F3FC-200D-2640-FE0F","non_qualified":"1F9D9-1F3FC-200D-2640","image":"1f9d9-1f3fc-200d-2640-fe0f.png","sheet_x":44,"sheet_y":32,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D9-1F3FC"},"1F3FD":{"unified":"1F9D9-1F3FD-200D-2640-FE0F","non_qualified":"1F9D9-1F3FD-200D-2640","image":"1f9d9-1f3fd-200d-2640-fe0f.png","sheet_x":44,"sheet_y":33,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D9-1F3FD"},"1F3FE":{"unified":"1F9D9-1F3FE-200D-2640-FE0F","non_qualified":"1F9D9-1F3FE-200D-2640","image":"1f9d9-1f3fe-200d-2640-fe0f.png","sheet_x":44,"sheet_y":34,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D9-1F3FE"},"1F3FF":{"unified":"1F9D9-1F3FF-200D-2640-FE0F","non_qualified":"1F9D9-1F3FF-200D-2640","image":"1f9d9-1f3ff-200d-2640-fe0f.png","sheet_x":44,"sheet_y":35,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D9-1F3FF"}},"obsoletes":"1F9D9","a":"Female Mage","b":"1F9D9-200D-2640-FE0F","c":"1F9D9-200D-2640","k":[44,30],"o":10},"secret":{"a":"Circled Ideograph Secret","b":"3299-FE0F","c":"3299","j":["privacy","chinese","sshh","kanji","red-circle"],"k":[50,27],"o":1},"flag-nu":{"a":"Niue Flag","b":"1F1F3-1F1FA","k":[3,50]},"u55b6":{"a":"Squared Cjk Unified Ideograph-55b6","b":"1F23A","j":["japanese","opening hours","orange-square"],"k":[5,41]},"male_mage":{"skin_variations":{"1F3FB":{"unified":"1F9D9-1F3FB-200D-2642-FE0F","non_qualified":"1F9D9-1F3FB-200D-2642","image":"1f9d9-1f3fb-200d-2642-fe0f.png","sheet_x":44,"sheet_y":37,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9D9-1F3FC-200D-2642-FE0F","non_qualified":"1F9D9-1F3FC-200D-2642","image":"1f9d9-1f3fc-200d-2642-fe0f.png","sheet_x":44,"sheet_y":38,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9D9-1F3FD-200D-2642-FE0F","non_qualified":"1F9D9-1F3FD-200D-2642","image":"1f9d9-1f3fd-200d-2642-fe0f.png","sheet_x":44,"sheet_y":39,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9D9-1F3FE-200D-2642-FE0F","non_qualified":"1F9D9-1F3FE-200D-2642","image":"1f9d9-1f3fe-200d-2642-fe0f.png","sheet_x":44,"sheet_y":40,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9D9-1F3FF-200D-2642-FE0F","non_qualified":"1F9D9-1F3FF-200D-2642","image":"1f9d9-1f3ff-200d-2642-fe0f.png","sheet_x":44,"sheet_y":41,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Male Mage","b":"1F9D9-200D-2642-FE0F","c":"1F9D9-200D-2642","k":[44,36],"o":10},"cloud":{"a":"Cloud","b":"2601-FE0F","c":"2601","j":["weather","sky"],"k":[47,17],"o":1},"flag-nz":{"a":"New Zealand Flag","b":"1F1F3-1F1FF","k":[3,51]},"partly_sunny":{"a":"Sun Behind Cloud","b":"26C5","j":["weather","nature","cloudy","morning","fall","spring"],"k":[48,29],"o":5},"fairy":{"skin_variations":{"1F3FB":{"unified":"1F9DA-1F3FB","non_qualified":null,"image":"1f9da-1f3fb.png","sheet_x":45,"sheet_y":9,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DA-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9DA-1F3FC","non_qualified":null,"image":"1f9da-1f3fc.png","sheet_x":45,"sheet_y":10,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DA-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9DA-1F3FD","non_qualified":null,"image":"1f9da-1f3fd.png","sheet_x":45,"sheet_y":11,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DA-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9DA-1F3FE","non_qualified":null,"image":"1f9da-1f3fe.png","sheet_x":45,"sheet_y":12,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DA-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9DA-1F3FF","non_qualified":null,"image":"1f9da-1f3ff.png","sheet_x":45,"sheet_y":13,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DA-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9DA-200D-2640-FE0F","a":"Fairy","b":"1F9DA","k":[45,8],"o":10},"u6e80":{"a":"Squared Cjk Unified Ideograph-6e80","b":"1F235","j":["full","chinese","japanese","red-square","kanji"],"k":[5,36]},"black_small_square":{"a":"Black Small Square","b":"25AA-FE0F","c":"25AA","j":["shape","icon"],"k":[47,8],"o":1},"thunder_cloud_and_rain":{"a":"Thunder Cloud and Rain","b":"26C8-FE0F","c":"26C8","k":[48,30],"o":5},"female_fairy":{"skin_variations":{"1F3FB":{"unified":"1F9DA-1F3FB-200D-2640-FE0F","non_qualified":"1F9DA-1F3FB-200D-2640","image":"1f9da-1f3fb-200d-2640-fe0f.png","sheet_x":44,"sheet_y":49,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DA-1F3FB"},"1F3FC":{"unified":"1F9DA-1F3FC-200D-2640-FE0F","non_qualified":"1F9DA-1F3FC-200D-2640","image":"1f9da-1f3fc-200d-2640-fe0f.png","sheet_x":44,"sheet_y":50,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DA-1F3FC"},"1F3FD":{"unified":"1F9DA-1F3FD-200D-2640-FE0F","non_qualified":"1F9DA-1F3FD-200D-2640","image":"1f9da-1f3fd-200d-2640-fe0f.png","sheet_x":44,"sheet_y":51,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DA-1F3FD"},"1F3FE":{"unified":"1F9DA-1F3FE-200D-2640-FE0F","non_qualified":"1F9DA-1F3FE-200D-2640","image":"1f9da-1f3fe-200d-2640-fe0f.png","sheet_x":45,"sheet_y":0,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DA-1F3FE"},"1F3FF":{"unified":"1F9DA-1F3FF-200D-2640-FE0F","non_qualified":"1F9DA-1F3FF-200D-2640","image":"1f9da-1f3ff-200d-2640-fe0f.png","sheet_x":45,"sheet_y":1,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DA-1F3FF"}},"obsoletes":"1F9DA","a":"Female Fairy","b":"1F9DA-200D-2640-FE0F","c":"1F9DA-200D-2640","k":[44,48],"o":10},"flag-om":{"a":"Oman Flag","b":"1F1F4-1F1F2","k":[4,0]},"white_small_square":{"a":"White Small Square","b":"25AB-FE0F","c":"25AB","j":["shape","icon"],"k":[47,9],"o":1},"flag-pa":{"a":"Panama Flag","b":"1F1F5-1F1E6","k":[4,1]},"mostly_sunny":{"a":"Mostly Sunny","b":"1F324-FE0F","c":"1F324","k":[6,26],"n":["sun_small_cloud"],"o":7},"male_fairy":{"skin_variations":{"1F3FB":{"unified":"1F9DA-1F3FB-200D-2642-FE0F","non_qualified":"1F9DA-1F3FB-200D-2642","image":"1f9da-1f3fb-200d-2642-fe0f.png","sheet_x":45,"sheet_y":3,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9DA-1F3FC-200D-2642-FE0F","non_qualified":"1F9DA-1F3FC-200D-2642","image":"1f9da-1f3fc-200d-2642-fe0f.png","sheet_x":45,"sheet_y":4,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9DA-1F3FD-200D-2642-FE0F","non_qualified":"1F9DA-1F3FD-200D-2642","image":"1f9da-1f3fd-200d-2642-fe0f.png","sheet_x":45,"sheet_y":5,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9DA-1F3FE-200D-2642-FE0F","non_qualified":"1F9DA-1F3FE-200D-2642","image":"1f9da-1f3fe-200d-2642-fe0f.png","sheet_x":45,"sheet_y":6,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9DA-1F3FF-200D-2642-FE0F","non_qualified":"1F9DA-1F3FF-200D-2642","image":"1f9da-1f3ff-200d-2642-fe0f.png","sheet_x":45,"sheet_y":7,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Male Fairy","b":"1F9DA-200D-2642-FE0F","c":"1F9DA-200D-2642","k":[45,2],"o":10},"barely_sunny":{"a":"Barely Sunny","b":"1F325-FE0F","c":"1F325","k":[6,27],"n":["sun_behind_cloud"],"o":7},"white_medium_square":{"a":"White Medium Square","b":"25FB-FE0F","c":"25FB","j":["shape","stone","icon"],"k":[47,12],"o":3},"flag-pe":{"a":"Peru Flag","b":"1F1F5-1F1EA","k":[4,2]},"vampire":{"skin_variations":{"1F3FB":{"unified":"1F9DB-1F3FB","non_qualified":null,"image":"1f9db-1f3fb.png","sheet_x":45,"sheet_y":27,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DB-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9DB-1F3FC","non_qualified":null,"image":"1f9db-1f3fc.png","sheet_x":45,"sheet_y":28,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DB-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9DB-1F3FD","non_qualified":null,"image":"1f9db-1f3fd.png","sheet_x":45,"sheet_y":29,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DB-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9DB-1F3FE","non_qualified":null,"image":"1f9db-1f3fe.png","sheet_x":45,"sheet_y":30,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DB-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9DB-1F3FF","non_qualified":null,"image":"1f9db-1f3ff.png","sheet_x":45,"sheet_y":31,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DB-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9DB-200D-2640-FE0F","a":"Vampire","b":"1F9DB","k":[45,26],"o":10},"female_vampire":{"skin_variations":{"1F3FB":{"unified":"1F9DB-1F3FB-200D-2640-FE0F","non_qualified":"1F9DB-1F3FB-200D-2640","image":"1f9db-1f3fb-200d-2640-fe0f.png","sheet_x":45,"sheet_y":15,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DB-1F3FB"},"1F3FC":{"unified":"1F9DB-1F3FC-200D-2640-FE0F","non_qualified":"1F9DB-1F3FC-200D-2640","image":"1f9db-1f3fc-200d-2640-fe0f.png","sheet_x":45,"sheet_y":16,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DB-1F3FC"},"1F3FD":{"unified":"1F9DB-1F3FD-200D-2640-FE0F","non_qualified":"1F9DB-1F3FD-200D-2640","image":"1f9db-1f3fd-200d-2640-fe0f.png","sheet_x":45,"sheet_y":17,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DB-1F3FD"},"1F3FE":{"unified":"1F9DB-1F3FE-200D-2640-FE0F","non_qualified":"1F9DB-1F3FE-200D-2640","image":"1f9db-1f3fe-200d-2640-fe0f.png","sheet_x":45,"sheet_y":18,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DB-1F3FE"},"1F3FF":{"unified":"1F9DB-1F3FF-200D-2640-FE0F","non_qualified":"1F9DB-1F3FF-200D-2640","image":"1f9db-1f3ff-200d-2640-fe0f.png","sheet_x":45,"sheet_y":19,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DB-1F3FF"}},"obsoletes":"1F9DB","a":"Female Vampire","b":"1F9DB-200D-2640-FE0F","c":"1F9DB-200D-2640","k":[45,14],"o":10},"partly_sunny_rain":{"a":"Partly Sunny Rain","b":"1F326-FE0F","c":"1F326","k":[6,28],"n":["sun_behind_rain_cloud"],"o":7},"flag-pf":{"a":"French Polynesia Flag","b":"1F1F5-1F1EB","k":[4,3]},"black_medium_square":{"a":"Black Medium Square","b":"25FC-FE0F","c":"25FC","j":["shape","button","icon"],"k":[47,13],"o":3},"white_medium_small_square":{"a":"White Medium Small Square","b":"25FD","j":["shape","stone","icon","button"],"k":[47,14],"o":3},"rain_cloud":{"a":"Rain Cloud","b":"1F327-FE0F","c":"1F327","k":[6,29],"o":7},"flag-pg":{"a":"Papua New Guinea Flag","b":"1F1F5-1F1EC","k":[4,4]},"male_vampire":{"skin_variations":{"1F3FB":{"unified":"1F9DB-1F3FB-200D-2642-FE0F","non_qualified":"1F9DB-1F3FB-200D-2642","image":"1f9db-1f3fb-200d-2642-fe0f.png","sheet_x":45,"sheet_y":21,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9DB-1F3FC-200D-2642-FE0F","non_qualified":"1F9DB-1F3FC-200D-2642","image":"1f9db-1f3fc-200d-2642-fe0f.png","sheet_x":45,"sheet_y":22,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9DB-1F3FD-200D-2642-FE0F","non_qualified":"1F9DB-1F3FD-200D-2642","image":"1f9db-1f3fd-200d-2642-fe0f.png","sheet_x":45,"sheet_y":23,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9DB-1F3FE-200D-2642-FE0F","non_qualified":"1F9DB-1F3FE-200D-2642","image":"1f9db-1f3fe-200d-2642-fe0f.png","sheet_x":45,"sheet_y":24,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9DB-1F3FF-200D-2642-FE0F","non_qualified":"1F9DB-1F3FF-200D-2642","image":"1f9db-1f3ff-200d-2642-fe0f.png","sheet_x":45,"sheet_y":25,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Male Vampire","b":"1F9DB-200D-2642-FE0F","c":"1F9DB-200D-2642","k":[45,20],"o":10},"flag-ph":{"a":"Philippines Flag","b":"1F1F5-1F1ED","k":[4,5]},"merperson":{"skin_variations":{"1F3FB":{"unified":"1F9DC-1F3FB","non_qualified":null,"image":"1f9dc-1f3fb.png","sheet_x":45,"sheet_y":45,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DC-1F3FB-200D-2642-FE0F"},"1F3FC":{"unified":"1F9DC-1F3FC","non_qualified":null,"image":"1f9dc-1f3fc.png","sheet_x":45,"sheet_y":46,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DC-1F3FC-200D-2642-FE0F"},"1F3FD":{"unified":"1F9DC-1F3FD","non_qualified":null,"image":"1f9dc-1f3fd.png","sheet_x":45,"sheet_y":47,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DC-1F3FD-200D-2642-FE0F"},"1F3FE":{"unified":"1F9DC-1F3FE","non_qualified":null,"image":"1f9dc-1f3fe.png","sheet_x":45,"sheet_y":48,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DC-1F3FE-200D-2642-FE0F"},"1F3FF":{"unified":"1F9DC-1F3FF","non_qualified":null,"image":"1f9dc-1f3ff.png","sheet_x":45,"sheet_y":49,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DC-1F3FF-200D-2642-FE0F"}},"obsoleted_by":"1F9DC-200D-2642-FE0F","a":"Merperson","b":"1F9DC","k":[45,44],"o":10},"black_medium_small_square":{"a":"Black Medium Small Square","b":"25FE","j":["icon","shape","button"],"k":[47,15],"o":3},"snow_cloud":{"a":"Snow Cloud","b":"1F328-FE0F","c":"1F328","k":[6,30],"o":7},"lightning":{"a":"Lightning","b":"1F329-FE0F","c":"1F329","k":[6,31],"n":["lightning_cloud"],"o":7},"black_large_square":{"a":"Black Large Square","b":"2B1B","j":["shape","icon","button"],"k":[50,20],"o":5},"mermaid":{"skin_variations":{"1F3FB":{"unified":"1F9DC-1F3FB-200D-2640-FE0F","non_qualified":"1F9DC-1F3FB-200D-2640","image":"1f9dc-1f3fb-200d-2640-fe0f.png","sheet_x":45,"sheet_y":33,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9DC-1F3FC-200D-2640-FE0F","non_qualified":"1F9DC-1F3FC-200D-2640","image":"1f9dc-1f3fc-200d-2640-fe0f.png","sheet_x":45,"sheet_y":34,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9DC-1F3FD-200D-2640-FE0F","non_qualified":"1F9DC-1F3FD-200D-2640","image":"1f9dc-1f3fd-200d-2640-fe0f.png","sheet_x":45,"sheet_y":35,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9DC-1F3FE-200D-2640-FE0F","non_qualified":"1F9DC-1F3FE-200D-2640","image":"1f9dc-1f3fe-200d-2640-fe0f.png","sheet_x":45,"sheet_y":36,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9DC-1F3FF-200D-2640-FE0F","non_qualified":"1F9DC-1F3FF-200D-2640","image":"1f9dc-1f3ff-200d-2640-fe0f.png","sheet_x":45,"sheet_y":37,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Mermaid","b":"1F9DC-200D-2640-FE0F","c":"1F9DC-200D-2640","k":[45,32],"o":10},"flag-pk":{"a":"Pakistan Flag","b":"1F1F5-1F1F0","k":[4,6]},"merman":{"skin_variations":{"1F3FB":{"unified":"1F9DC-1F3FB-200D-2642-FE0F","non_qualified":"1F9DC-1F3FB-200D-2642","image":"1f9dc-1f3fb-200d-2642-fe0f.png","sheet_x":45,"sheet_y":39,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DC-1F3FB"},"1F3FC":{"unified":"1F9DC-1F3FC-200D-2642-FE0F","non_qualified":"1F9DC-1F3FC-200D-2642","image":"1f9dc-1f3fc-200d-2642-fe0f.png","sheet_x":45,"sheet_y":40,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DC-1F3FC"},"1F3FD":{"unified":"1F9DC-1F3FD-200D-2642-FE0F","non_qualified":"1F9DC-1F3FD-200D-2642","image":"1f9dc-1f3fd-200d-2642-fe0f.png","sheet_x":45,"sheet_y":41,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DC-1F3FD"},"1F3FE":{"unified":"1F9DC-1F3FE-200D-2642-FE0F","non_qualified":"1F9DC-1F3FE-200D-2642","image":"1f9dc-1f3fe-200d-2642-fe0f.png","sheet_x":45,"sheet_y":42,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DC-1F3FE"},"1F3FF":{"unified":"1F9DC-1F3FF-200D-2642-FE0F","non_qualified":"1F9DC-1F3FF-200D-2642","image":"1f9dc-1f3ff-200d-2642-fe0f.png","sheet_x":45,"sheet_y":43,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DC-1F3FF"}},"obsoletes":"1F9DC","a":"Merman","b":"1F9DC-200D-2642-FE0F","c":"1F9DC-200D-2642","k":[45,38],"o":10},"white_large_square":{"a":"White Large Square","b":"2B1C","j":["shape","icon","stone","button"],"k":[50,21],"o":5},"tornado":{"a":"Tornado","b":"1F32A-FE0F","c":"1F32A","j":["weather","cyclone","twister"],"k":[6,32],"n":["tornado_cloud"],"o":7},"flag-pl":{"a":"Poland Flag","b":"1F1F5-1F1F1","k":[4,7]},"elf":{"skin_variations":{"1F3FB":{"unified":"1F9DD-1F3FB","non_qualified":null,"image":"1f9dd-1f3fb.png","sheet_x":46,"sheet_y":11,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DD-1F3FB-200D-2642-FE0F"},"1F3FC":{"unified":"1F9DD-1F3FC","non_qualified":null,"image":"1f9dd-1f3fc.png","sheet_x":46,"sheet_y":12,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DD-1F3FC-200D-2642-FE0F"},"1F3FD":{"unified":"1F9DD-1F3FD","non_qualified":null,"image":"1f9dd-1f3fd.png","sheet_x":46,"sheet_y":13,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DD-1F3FD-200D-2642-FE0F"},"1F3FE":{"unified":"1F9DD-1F3FE","non_qualified":null,"image":"1f9dd-1f3fe.png","sheet_x":46,"sheet_y":14,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DD-1F3FE-200D-2642-FE0F"},"1F3FF":{"unified":"1F9DD-1F3FF","non_qualified":null,"image":"1f9dd-1f3ff.png","sheet_x":46,"sheet_y":15,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DD-1F3FF-200D-2642-FE0F"}},"obsoleted_by":"1F9DD-200D-2642-FE0F","a":"Elf","b":"1F9DD","k":[46,10],"o":10},"fog":{"a":"Fog","b":"1F32B-FE0F","c":"1F32B","j":["weather"],"k":[6,33],"o":7},"large_orange_diamond":{"a":"Large Orange Diamond","b":"1F536","j":["shape","jewel","gem"],"k":[28,4]},"flag-pn":{"a":"Pitcairn Islands Flag","b":"1F1F5-1F1F3","k":[4,9]},"wind_blowing_face":{"a":"Wind Blowing Face","b":"1F32C-FE0F","c":"1F32C","k":[6,34],"o":7},"female_elf":{"skin_variations":{"1F3FB":{"unified":"1F9DD-1F3FB-200D-2640-FE0F","non_qualified":"1F9DD-1F3FB-200D-2640","image":"1f9dd-1f3fb-200d-2640-fe0f.png","sheet_x":45,"sheet_y":51,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9DD-1F3FC-200D-2640-FE0F","non_qualified":"1F9DD-1F3FC-200D-2640","image":"1f9dd-1f3fc-200d-2640-fe0f.png","sheet_x":46,"sheet_y":0,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9DD-1F3FD-200D-2640-FE0F","non_qualified":"1F9DD-1F3FD-200D-2640","image":"1f9dd-1f3fd-200d-2640-fe0f.png","sheet_x":46,"sheet_y":1,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9DD-1F3FE-200D-2640-FE0F","non_qualified":"1F9DD-1F3FE-200D-2640","image":"1f9dd-1f3fe-200d-2640-fe0f.png","sheet_x":46,"sheet_y":2,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9DD-1F3FF-200D-2640-FE0F","non_qualified":"1F9DD-1F3FF-200D-2640","image":"1f9dd-1f3ff-200d-2640-fe0f.png","sheet_x":46,"sheet_y":3,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Female Elf","b":"1F9DD-200D-2640-FE0F","c":"1F9DD-200D-2640","k":[45,50],"o":10},"large_blue_diamond":{"a":"Large Blue Diamond","b":"1F537","j":["shape","jewel","gem"],"k":[28,5]},"male_elf":{"skin_variations":{"1F3FB":{"unified":"1F9DD-1F3FB-200D-2642-FE0F","non_qualified":"1F9DD-1F3FB-200D-2642","image":"1f9dd-1f3fb-200d-2642-fe0f.png","sheet_x":46,"sheet_y":5,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DD-1F3FB"},"1F3FC":{"unified":"1F9DD-1F3FC-200D-2642-FE0F","non_qualified":"1F9DD-1F3FC-200D-2642","image":"1f9dd-1f3fc-200d-2642-fe0f.png","sheet_x":46,"sheet_y":6,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DD-1F3FC"},"1F3FD":{"unified":"1F9DD-1F3FD-200D-2642-FE0F","non_qualified":"1F9DD-1F3FD-200D-2642","image":"1f9dd-1f3fd-200d-2642-fe0f.png","sheet_x":46,"sheet_y":7,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DD-1F3FD"},"1F3FE":{"unified":"1F9DD-1F3FE-200D-2642-FE0F","non_qualified":"1F9DD-1F3FE-200D-2642","image":"1f9dd-1f3fe-200d-2642-fe0f.png","sheet_x":46,"sheet_y":8,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DD-1F3FE"},"1F3FF":{"unified":"1F9DD-1F3FF-200D-2642-FE0F","non_qualified":"1F9DD-1F3FF-200D-2642","image":"1f9dd-1f3ff-200d-2642-fe0f.png","sheet_x":46,"sheet_y":9,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DD-1F3FF"}},"obsoletes":"1F9DD","a":"Male Elf","b":"1F9DD-200D-2642-FE0F","c":"1F9DD-200D-2642","k":[46,4],"o":10},"small_orange_diamond":{"a":"Small Orange Diamond","b":"1F538","j":["shape","jewel","gem"],"k":[28,6]},"flag-pr":{"a":"Puerto Rico Flag","b":"1F1F5-1F1F7","k":[4,10]},"cyclone":{"a":"Cyclone","b":"1F300","j":["weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado","hurricane","typhoon"],"k":[5,44]},"rainbow":{"a":"Rainbow","b":"1F308","j":["nature","happy","unicorn_face","photo","sky","spring"],"k":[6,0]},"small_blue_diamond":{"a":"Small Blue Diamond","b":"1F539","j":["shape","jewel","gem"],"k":[28,7]},"genie":{"obsoleted_by":"1F9DE-200D-2642-FE0F","a":"Genie","b":"1F9DE","k":[46,18],"o":10},"flag-ps":{"a":"Palestinian Territories Flag","b":"1F1F5-1F1F8","k":[4,11]},"small_red_triangle":{"a":"Up-Pointing Red Triangle","b":"1F53A","j":["shape","direction","up","top"],"k":[28,8]},"closed_umbrella":{"a":"Closed Umbrella","b":"1F302","j":["weather","rain","drizzle"],"k":[5,46]},"female_genie":{"a":"Female Genie","b":"1F9DE-200D-2640-FE0F","c":"1F9DE-200D-2640","k":[46,16],"o":10},"flag-pt":{"a":"Portugal Flag","b":"1F1F5-1F1F9","k":[4,12]},"flag-pw":{"a":"Palau Flag","b":"1F1F5-1F1FC","k":[4,13]},"small_red_triangle_down":{"a":"Down-Pointing Red Triangle","b":"1F53B","j":["shape","direction","bottom"],"k":[28,9]},"umbrella":{"a":"Umbrella","b":"2602-FE0F","c":"2602","j":["rainy","weather","spring"],"k":[47,18],"o":1},"male_genie":{"obsoletes":"1F9DE","a":"Male Genie","b":"1F9DE-200D-2642-FE0F","c":"1F9DE-200D-2642","k":[46,17],"o":10},"zombie":{"obsoleted_by":"1F9DF-200D-2642-FE0F","a":"Zombie","b":"1F9DF","k":[46,21],"o":10},"flag-py":{"a":"Paraguay Flag","b":"1F1F5-1F1FE","k":[4,14]},"diamond_shape_with_a_dot_inside":{"a":"Diamond Shape with a Dot Inside","b":"1F4A0","j":["jewel","blue","gem","crystal","fancy"],"k":[25,6]},"umbrella_with_rain_drops":{"a":"Umbrella with Rain Drops","b":"2614","k":[47,23],"o":4},"radio_button":{"a":"Radio Button","b":"1F518","j":["input","old","music","circle"],"k":[27,26]},"female_zombie":{"a":"Female Zombie","b":"1F9DF-200D-2640-FE0F","c":"1F9DF-200D-2640","k":[46,19],"o":10},"flag-qa":{"a":"Qatar Flag","b":"1F1F6-1F1E6","k":[4,15]},"umbrella_on_ground":{"a":"Umbrella on Ground","b":"26F1-FE0F","c":"26F1","k":[48,39],"o":5},"black_square_button":{"a":"Black Square Button","b":"1F532","j":["shape","input","frame"],"k":[28,0]},"zap":{"a":"High Voltage Sign","b":"26A1","j":["thunder","weather","lightning bolt","fast"],"k":[48,21],"o":4},"male_zombie":{"obsoletes":"1F9DF","a":"Male Zombie","b":"1F9DF-200D-2642-FE0F","c":"1F9DF-200D-2642","k":[46,20],"o":10},"flag-ro":{"a":"Romania Flag","b":"1F1F7-1F1F4","k":[4,17]},"snowflake":{"a":"Snowflake","b":"2744-FE0F","c":"2744","j":["winter","season","cold","weather","christmas","xmas"],"k":[49,51],"o":1},"white_square_button":{"a":"White Square Button","b":"1F533","j":["shape","input"],"k":[28,1]},"person_frowning":{"skin_variations":{"1F3FB":{"unified":"1F64D-1F3FB","non_qualified":null,"image":"1f64d-1f3fb.png","sheet_x":33,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F64D-1F3FC","non_qualified":null,"image":"1f64d-1f3fc.png","sheet_x":33,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F64D-1F3FD","non_qualified":null,"image":"1f64d-1f3fd.png","sheet_x":33,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F64D-1F3FE","non_qualified":null,"image":"1f64d-1f3fe.png","sheet_x":33,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F64D-1F3FF","non_qualified":null,"image":"1f64d-1f3ff.png","sheet_x":33,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F64D-200D-2640-FE0F","a":"Person Frowning","b":"1F64D","k":[33,30]},"flag-rs":{"a":"Serbia Flag","b":"1F1F7-1F1F8","k":[4,18]},"man-frowning":{"skin_variations":{"1F3FB":{"unified":"1F64D-1F3FB-200D-2642-FE0F","non_qualified":"1F64D-1F3FB-200D-2642","image":"1f64d-1f3fb-200d-2642-fe0f.png","sheet_x":33,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F64D-1F3FC-200D-2642-FE0F","non_qualified":"1F64D-1F3FC-200D-2642","image":"1f64d-1f3fc-200d-2642-fe0f.png","sheet_x":33,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F64D-1F3FD-200D-2642-FE0F","non_qualified":"1F64D-1F3FD-200D-2642","image":"1f64d-1f3fd-200d-2642-fe0f.png","sheet_x":33,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F64D-1F3FE-200D-2642-FE0F","non_qualified":"1F64D-1F3FE-200D-2642","image":"1f64d-1f3fe-200d-2642-fe0f.png","sheet_x":33,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F64D-1F3FF-200D-2642-FE0F","non_qualified":"1F64D-1F3FF-200D-2642","image":"1f64d-1f3ff-200d-2642-fe0f.png","sheet_x":33,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Frowning","b":"1F64D-200D-2642-FE0F","c":"1F64D-200D-2642","k":[33,24]},"white_circle":{"a":"Medium White Circle","b":"26AA","j":["shape","round"],"k":[48,22],"o":4},"snowman":{"a":"Snowman","b":"2603-FE0F","c":"2603","j":["winter","season","cold","weather","christmas","xmas","frozen","without_snow"],"k":[47,19],"o":1},"snowman_without_snow":{"a":"Snowman Without Snow","b":"26C4","k":[48,28],"o":5},"ru":{"a":"Russia Flag","b":"1F1F7-1F1FA","j":["russian","federation","flag","nation","country","banner"],"k":[4,19],"n":["flag-ru"]},"black_circle":{"a":"Medium Black Circle","b":"26AB","j":["shape","button","round"],"k":[48,23],"o":4},"woman-frowning":{"skin_variations":{"1F3FB":{"unified":"1F64D-1F3FB-200D-2640-FE0F","non_qualified":"1F64D-1F3FB-200D-2640","image":"1f64d-1f3fb-200d-2640-fe0f.png","sheet_x":33,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F64D-1F3FC-200D-2640-FE0F","non_qualified":"1F64D-1F3FC-200D-2640","image":"1f64d-1f3fc-200d-2640-fe0f.png","sheet_x":33,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F64D-1F3FD-200D-2640-FE0F","non_qualified":"1F64D-1F3FD-200D-2640","image":"1f64d-1f3fd-200d-2640-fe0f.png","sheet_x":33,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F64D-1F3FE-200D-2640-FE0F","non_qualified":"1F64D-1F3FE-200D-2640","image":"1f64d-1f3fe-200d-2640-fe0f.png","sheet_x":33,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F64D-1F3FF-200D-2640-FE0F","non_qualified":"1F64D-1F3FF-200D-2640","image":"1f64d-1f3ff-200d-2640-fe0f.png","sheet_x":33,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F64D","a":"Woman Frowning","b":"1F64D-200D-2640-FE0F","c":"1F64D-200D-2640","k":[33,18]},"flag-rw":{"a":"Rwanda Flag","b":"1F1F7-1F1FC","k":[4,20]},"comet":{"a":"Comet","b":"2604-FE0F","c":"2604","j":["space"],"k":[47,20],"o":1},"person_with_pouting_face":{"skin_variations":{"1F3FB":{"unified":"1F64E-1F3FB","non_qualified":null,"image":"1f64e-1f3fb.png","sheet_x":33,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F64E-1F3FC","non_qualified":null,"image":"1f64e-1f3fc.png","sheet_x":33,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F64E-1F3FD","non_qualified":null,"image":"1f64e-1f3fd.png","sheet_x":33,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F64E-1F3FE","non_qualified":null,"image":"1f64e-1f3fe.png","sheet_x":34,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F64E-1F3FF","non_qualified":null,"image":"1f64e-1f3ff.png","sheet_x":34,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F64E-200D-2640-FE0F","a":"Person with Pouting Face","b":"1F64E","k":[33,48]},"red_circle":{"a":"Large Red Circle","b":"1F534","j":["shape","error","danger"],"k":[28,2]},"large_blue_circle":{"a":"Large Blue Circle","b":"1F535","j":["shape","icon","button"],"k":[28,3]},"man-pouting":{"skin_variations":{"1F3FB":{"unified":"1F64E-1F3FB-200D-2642-FE0F","non_qualified":"1F64E-1F3FB-200D-2642","image":"1f64e-1f3fb-200d-2642-fe0f.png","sheet_x":33,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F64E-1F3FC-200D-2642-FE0F","non_qualified":"1F64E-1F3FC-200D-2642","image":"1f64e-1f3fc-200d-2642-fe0f.png","sheet_x":33,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F64E-1F3FD-200D-2642-FE0F","non_qualified":"1F64E-1F3FD-200D-2642","image":"1f64e-1f3fd-200d-2642-fe0f.png","sheet_x":33,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F64E-1F3FE-200D-2642-FE0F","non_qualified":"1F64E-1F3FE-200D-2642","image":"1f64e-1f3fe-200d-2642-fe0f.png","sheet_x":33,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F64E-1F3FF-200D-2642-FE0F","non_qualified":"1F64E-1F3FF-200D-2642","image":"1f64e-1f3ff-200d-2642-fe0f.png","sheet_x":33,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Pouting","b":"1F64E-200D-2642-FE0F","c":"1F64E-200D-2642","k":[33,42]},"flag-sa":{"a":"Saudi Arabia Flag","b":"1F1F8-1F1E6","k":[4,21]},"fire":{"a":"Fire","b":"1F525","j":["hot","cook","flame"],"k":[27,39]},"woman-pouting":{"skin_variations":{"1F3FB":{"unified":"1F64E-1F3FB-200D-2640-FE0F","non_qualified":"1F64E-1F3FB-200D-2640","image":"1f64e-1f3fb-200d-2640-fe0f.png","sheet_x":33,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F64E-1F3FC-200D-2640-FE0F","non_qualified":"1F64E-1F3FC-200D-2640","image":"1f64e-1f3fc-200d-2640-fe0f.png","sheet_x":33,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F64E-1F3FD-200D-2640-FE0F","non_qualified":"1F64E-1F3FD-200D-2640","image":"1f64e-1f3fd-200d-2640-fe0f.png","sheet_x":33,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F64E-1F3FE-200D-2640-FE0F","non_qualified":"1F64E-1F3FE-200D-2640","image":"1f64e-1f3fe-200d-2640-fe0f.png","sheet_x":33,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F64E-1F3FF-200D-2640-FE0F","non_qualified":"1F64E-1F3FF-200D-2640","image":"1f64e-1f3ff-200d-2640-fe0f.png","sheet_x":33,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F64E","a":"Woman Pouting","b":"1F64E-200D-2640-FE0F","c":"1F64E-200D-2640","k":[33,36]},"flag-sb":{"a":"Solomon Islands Flag","b":"1F1F8-1F1E7","k":[4,22]},"droplet":{"a":"Droplet","b":"1F4A7","j":["water","drip","faucet","spring"],"k":[25,13]},"no_good":{"skin_variations":{"1F3FB":{"unified":"1F645-1F3FB","non_qualified":null,"image":"1f645-1f3fb.png","sheet_x":32,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F645-1F3FC","non_qualified":null,"image":"1f645-1f3fc.png","sheet_x":32,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F645-1F3FD","non_qualified":null,"image":"1f645-1f3fd.png","sheet_x":32,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F645-1F3FE","non_qualified":null,"image":"1f645-1f3fe.png","sheet_x":32,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F645-1F3FF","non_qualified":null,"image":"1f645-1f3ff.png","sheet_x":32,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F645-200D-2640-FE0F","a":"Face with No Good Gesture","b":"1F645","k":[32,1]},"flag-sc":{"a":"Seychelles Flag","b":"1F1F8-1F1E8","k":[4,23]},"ocean":{"a":"Water Wave","b":"1F30A","j":["sea","water","wave","nature","tsunami","disaster"],"k":[6,2]},"man-gesturing-no":{"skin_variations":{"1F3FB":{"unified":"1F645-1F3FB-200D-2642-FE0F","non_qualified":"1F645-1F3FB-200D-2642","image":"1f645-1f3fb-200d-2642-fe0f.png","sheet_x":31,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F645-1F3FC-200D-2642-FE0F","non_qualified":"1F645-1F3FC-200D-2642","image":"1f645-1f3fc-200d-2642-fe0f.png","sheet_x":31,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F645-1F3FD-200D-2642-FE0F","non_qualified":"1F645-1F3FD-200D-2642","image":"1f645-1f3fd-200d-2642-fe0f.png","sheet_x":31,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F645-1F3FE-200D-2642-FE0F","non_qualified":"1F645-1F3FE-200D-2642","image":"1f645-1f3fe-200d-2642-fe0f.png","sheet_x":31,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F645-1F3FF-200D-2642-FE0F","non_qualified":"1F645-1F3FF-200D-2642","image":"1f645-1f3ff-200d-2642-fe0f.png","sheet_x":32,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Gesturing No","b":"1F645-200D-2642-FE0F","c":"1F645-200D-2642","k":[31,47]},"flag-sd":{"a":"Sudan Flag","b":"1F1F8-1F1E9","k":[4,24]},"woman-gesturing-no":{"skin_variations":{"1F3FB":{"unified":"1F645-1F3FB-200D-2640-FE0F","non_qualified":"1F645-1F3FB-200D-2640","image":"1f645-1f3fb-200d-2640-fe0f.png","sheet_x":31,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F645-1F3FC-200D-2640-FE0F","non_qualified":"1F645-1F3FC-200D-2640","image":"1f645-1f3fc-200d-2640-fe0f.png","sheet_x":31,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F645-1F3FD-200D-2640-FE0F","non_qualified":"1F645-1F3FD-200D-2640","image":"1f645-1f3fd-200d-2640-fe0f.png","sheet_x":31,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F645-1F3FE-200D-2640-FE0F","non_qualified":"1F645-1F3FE-200D-2640","image":"1f645-1f3fe-200d-2640-fe0f.png","sheet_x":31,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F645-1F3FF-200D-2640-FE0F","non_qualified":"1F645-1F3FF-200D-2640","image":"1f645-1f3ff-200d-2640-fe0f.png","sheet_x":31,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F645","a":"Woman Gesturing No","b":"1F645-200D-2640-FE0F","c":"1F645-200D-2640","k":[31,41]},"flag-se":{"a":"Sweden Flag","b":"1F1F8-1F1EA","k":[4,25]},"flag-sg":{"a":"Singapore Flag","b":"1F1F8-1F1EC","k":[4,26]},"ok_woman":{"skin_variations":{"1F3FB":{"unified":"1F646-1F3FB","non_qualified":null,"image":"1f646-1f3fb.png","sheet_x":32,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F646-1F3FC","non_qualified":null,"image":"1f646-1f3fc.png","sheet_x":32,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F646-1F3FD","non_qualified":null,"image":"1f646-1f3fd.png","sheet_x":32,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F646-1F3FE","non_qualified":null,"image":"1f646-1f3fe.png","sheet_x":32,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F646-1F3FF","non_qualified":null,"image":"1f646-1f3ff.png","sheet_x":32,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F646-200D-2640-FE0F","a":"Face with Ok Gesture","b":"1F646","j":["women","girl","female","pink","human","woman"],"k":[32,19]},"flag-sh":{"a":"St. Helena Flag","b":"1F1F8-1F1ED","k":[4,27]},"man-gesturing-ok":{"skin_variations":{"1F3FB":{"unified":"1F646-1F3FB-200D-2642-FE0F","non_qualified":"1F646-1F3FB-200D-2642","image":"1f646-1f3fb-200d-2642-fe0f.png","sheet_x":32,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F646-1F3FC-200D-2642-FE0F","non_qualified":"1F646-1F3FC-200D-2642","image":"1f646-1f3fc-200d-2642-fe0f.png","sheet_x":32,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F646-1F3FD-200D-2642-FE0F","non_qualified":"1F646-1F3FD-200D-2642","image":"1f646-1f3fd-200d-2642-fe0f.png","sheet_x":32,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F646-1F3FE-200D-2642-FE0F","non_qualified":"1F646-1F3FE-200D-2642","image":"1f646-1f3fe-200d-2642-fe0f.png","sheet_x":32,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F646-1F3FF-200D-2642-FE0F","non_qualified":"1F646-1F3FF-200D-2642","image":"1f646-1f3ff-200d-2642-fe0f.png","sheet_x":32,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Gesturing Ok","b":"1F646-200D-2642-FE0F","c":"1F646-200D-2642","k":[32,13]},"flag-si":{"a":"Slovenia Flag","b":"1F1F8-1F1EE","k":[4,28]},"woman-gesturing-ok":{"skin_variations":{"1F3FB":{"unified":"1F646-1F3FB-200D-2640-FE0F","non_qualified":"1F646-1F3FB-200D-2640","image":"1f646-1f3fb-200d-2640-fe0f.png","sheet_x":32,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F646-1F3FC-200D-2640-FE0F","non_qualified":"1F646-1F3FC-200D-2640","image":"1f646-1f3fc-200d-2640-fe0f.png","sheet_x":32,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F646-1F3FD-200D-2640-FE0F","non_qualified":"1F646-1F3FD-200D-2640","image":"1f646-1f3fd-200d-2640-fe0f.png","sheet_x":32,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F646-1F3FE-200D-2640-FE0F","non_qualified":"1F646-1F3FE-200D-2640","image":"1f646-1f3fe-200d-2640-fe0f.png","sheet_x":32,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F646-1F3FF-200D-2640-FE0F","non_qualified":"1F646-1F3FF-200D-2640","image":"1f646-1f3ff-200d-2640-fe0f.png","sheet_x":32,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F646","a":"Woman Gesturing Ok","b":"1F646-200D-2640-FE0F","c":"1F646-200D-2640","k":[32,7]},"information_desk_person":{"skin_variations":{"1F3FB":{"unified":"1F481-1F3FB","non_qualified":null,"image":"1f481-1f3fb.png","sheet_x":23,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F481-1F3FC","non_qualified":null,"image":"1f481-1f3fc.png","sheet_x":23,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F481-1F3FD","non_qualified":null,"image":"1f481-1f3fd.png","sheet_x":23,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F481-1F3FE","non_qualified":null,"image":"1f481-1f3fe.png","sheet_x":23,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F481-1F3FF","non_qualified":null,"image":"1f481-1f3ff.png","sheet_x":23,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F481-200D-2640-FE0F","a":"Information Desk Person","b":"1F481","k":[23,13]},"flag-sj":{"a":"Svalbard & Jan Mayen Flag","b":"1F1F8-1F1EF","k":[4,29]},"man-tipping-hand":{"skin_variations":{"1F3FB":{"unified":"1F481-1F3FB-200D-2642-FE0F","non_qualified":"1F481-1F3FB-200D-2642","image":"1f481-1f3fb-200d-2642-fe0f.png","sheet_x":23,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F481-1F3FC-200D-2642-FE0F","non_qualified":"1F481-1F3FC-200D-2642","image":"1f481-1f3fc-200d-2642-fe0f.png","sheet_x":23,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F481-1F3FD-200D-2642-FE0F","non_qualified":"1F481-1F3FD-200D-2642","image":"1f481-1f3fd-200d-2642-fe0f.png","sheet_x":23,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F481-1F3FE-200D-2642-FE0F","non_qualified":"1F481-1F3FE-200D-2642","image":"1f481-1f3fe-200d-2642-fe0f.png","sheet_x":23,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F481-1F3FF-200D-2642-FE0F","non_qualified":"1F481-1F3FF-200D-2642","image":"1f481-1f3ff-200d-2642-fe0f.png","sheet_x":23,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Tipping Hand","b":"1F481-200D-2642-FE0F","c":"1F481-200D-2642","k":[23,7]},"flag-sk":{"a":"Slovakia Flag","b":"1F1F8-1F1F0","k":[4,30]},"flag-sl":{"a":"Sierra Leone Flag","b":"1F1F8-1F1F1","k":[4,31]},"woman-tipping-hand":{"skin_variations":{"1F3FB":{"unified":"1F481-1F3FB-200D-2640-FE0F","non_qualified":"1F481-1F3FB-200D-2640","image":"1f481-1f3fb-200d-2640-fe0f.png","sheet_x":23,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F481-1F3FC-200D-2640-FE0F","non_qualified":"1F481-1F3FC-200D-2640","image":"1f481-1f3fc-200d-2640-fe0f.png","sheet_x":23,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F481-1F3FD-200D-2640-FE0F","non_qualified":"1F481-1F3FD-200D-2640","image":"1f481-1f3fd-200d-2640-fe0f.png","sheet_x":23,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F481-1F3FE-200D-2640-FE0F","non_qualified":"1F481-1F3FE-200D-2640","image":"1f481-1f3fe-200d-2640-fe0f.png","sheet_x":23,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F481-1F3FF-200D-2640-FE0F","non_qualified":"1F481-1F3FF-200D-2640","image":"1f481-1f3ff-200d-2640-fe0f.png","sheet_x":23,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F481","a":"Woman Tipping Hand","b":"1F481-200D-2640-FE0F","c":"1F481-200D-2640","k":[23,1]},"flag-sm":{"a":"San Marino Flag","b":"1F1F8-1F1F2","k":[4,32]},"raising_hand":{"skin_variations":{"1F3FB":{"unified":"1F64B-1F3FB","non_qualified":null,"image":"1f64b-1f3fb.png","sheet_x":33,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F64B-1F3FC","non_qualified":null,"image":"1f64b-1f3fc.png","sheet_x":33,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F64B-1F3FD","non_qualified":null,"image":"1f64b-1f3fd.png","sheet_x":33,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F64B-1F3FE","non_qualified":null,"image":"1f64b-1f3fe.png","sheet_x":33,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F64B-1F3FF","non_qualified":null,"image":"1f64b-1f3ff.png","sheet_x":33,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F64B-200D-2640-FE0F","a":"Happy Person Raising One Hand","b":"1F64B","k":[33,6]},"flag-sn":{"a":"Senegal Flag","b":"1F1F8-1F1F3","k":[4,33]},"man-raising-hand":{"skin_variations":{"1F3FB":{"unified":"1F64B-1F3FB-200D-2642-FE0F","non_qualified":"1F64B-1F3FB-200D-2642","image":"1f64b-1f3fb-200d-2642-fe0f.png","sheet_x":33,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F64B-1F3FC-200D-2642-FE0F","non_qualified":"1F64B-1F3FC-200D-2642","image":"1f64b-1f3fc-200d-2642-fe0f.png","sheet_x":33,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F64B-1F3FD-200D-2642-FE0F","non_qualified":"1F64B-1F3FD-200D-2642","image":"1f64b-1f3fd-200d-2642-fe0f.png","sheet_x":33,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F64B-1F3FE-200D-2642-FE0F","non_qualified":"1F64B-1F3FE-200D-2642","image":"1f64b-1f3fe-200d-2642-fe0f.png","sheet_x":33,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F64B-1F3FF-200D-2642-FE0F","non_qualified":"1F64B-1F3FF-200D-2642","image":"1f64b-1f3ff-200d-2642-fe0f.png","sheet_x":33,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Raising Hand","b":"1F64B-200D-2642-FE0F","c":"1F64B-200D-2642","k":[33,0]},"flag-so":{"a":"Somalia Flag","b":"1F1F8-1F1F4","k":[4,34]},"woman-raising-hand":{"skin_variations":{"1F3FB":{"unified":"1F64B-1F3FB-200D-2640-FE0F","non_qualified":"1F64B-1F3FB-200D-2640","image":"1f64b-1f3fb-200d-2640-fe0f.png","sheet_x":32,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F64B-1F3FC-200D-2640-FE0F","non_qualified":"1F64B-1F3FC-200D-2640","image":"1f64b-1f3fc-200d-2640-fe0f.png","sheet_x":32,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F64B-1F3FD-200D-2640-FE0F","non_qualified":"1F64B-1F3FD-200D-2640","image":"1f64b-1f3fd-200d-2640-fe0f.png","sheet_x":32,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F64B-1F3FE-200D-2640-FE0F","non_qualified":"1F64B-1F3FE-200D-2640","image":"1f64b-1f3fe-200d-2640-fe0f.png","sheet_x":32,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F64B-1F3FF-200D-2640-FE0F","non_qualified":"1F64B-1F3FF-200D-2640","image":"1f64b-1f3ff-200d-2640-fe0f.png","sheet_x":32,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F64B","a":"Woman Raising Hand","b":"1F64B-200D-2640-FE0F","c":"1F64B-200D-2640","k":[32,46]},"flag-sr":{"a":"Suriname Flag","b":"1F1F8-1F1F7","k":[4,35]},"bow":{"skin_variations":{"1F3FB":{"unified":"1F647-1F3FB","non_qualified":null,"image":"1f647-1f3fb.png","sheet_x":32,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F647-1F3FC","non_qualified":null,"image":"1f647-1f3fc.png","sheet_x":32,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F647-1F3FD","non_qualified":null,"image":"1f647-1f3fd.png","sheet_x":32,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F647-1F3FE","non_qualified":null,"image":"1f647-1f3fe.png","sheet_x":32,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F647-1F3FF","non_qualified":null,"image":"1f647-1f3ff.png","sheet_x":32,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F647-200D-2642-FE0F","a":"Person Bowing Deeply","b":"1F647","k":[32,37]},"man-bowing":{"skin_variations":{"1F3FB":{"unified":"1F647-1F3FB-200D-2642-FE0F","non_qualified":"1F647-1F3FB-200D-2642","image":"1f647-1f3fb-200d-2642-fe0f.png","sheet_x":32,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F647-1F3FC-200D-2642-FE0F","non_qualified":"1F647-1F3FC-200D-2642","image":"1f647-1f3fc-200d-2642-fe0f.png","sheet_x":32,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F647-1F3FD-200D-2642-FE0F","non_qualified":"1F647-1F3FD-200D-2642","image":"1f647-1f3fd-200d-2642-fe0f.png","sheet_x":32,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F647-1F3FE-200D-2642-FE0F","non_qualified":"1F647-1F3FE-200D-2642","image":"1f647-1f3fe-200d-2642-fe0f.png","sheet_x":32,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F647-1F3FF-200D-2642-FE0F","non_qualified":"1F647-1F3FF-200D-2642","image":"1f647-1f3ff-200d-2642-fe0f.png","sheet_x":32,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F647","a":"Man Bowing","b":"1F647-200D-2642-FE0F","c":"1F647-200D-2642","k":[32,31]},"flag-ss":{"a":"South Sudan Flag","b":"1F1F8-1F1F8","k":[4,36]},"woman-bowing":{"skin_variations":{"1F3FB":{"unified":"1F647-1F3FB-200D-2640-FE0F","non_qualified":"1F647-1F3FB-200D-2640","image":"1f647-1f3fb-200d-2640-fe0f.png","sheet_x":32,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F647-1F3FC-200D-2640-FE0F","non_qualified":"1F647-1F3FC-200D-2640","image":"1f647-1f3fc-200d-2640-fe0f.png","sheet_x":32,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F647-1F3FD-200D-2640-FE0F","non_qualified":"1F647-1F3FD-200D-2640","image":"1f647-1f3fd-200d-2640-fe0f.png","sheet_x":32,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F647-1F3FE-200D-2640-FE0F","non_qualified":"1F647-1F3FE-200D-2640","image":"1f647-1f3fe-200d-2640-fe0f.png","sheet_x":32,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F647-1F3FF-200D-2640-FE0F","non_qualified":"1F647-1F3FF-200D-2640","image":"1f647-1f3ff-200d-2640-fe0f.png","sheet_x":32,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Bowing","b":"1F647-200D-2640-FE0F","c":"1F647-200D-2640","k":[32,25]},"flag-st":{"a":"São Tomé & Príncipe Flag","b":"1F1F8-1F1F9","k":[4,37]},"face_palm":{"skin_variations":{"1F3FB":{"unified":"1F926-1F3FB","non_qualified":null,"image":"1f926-1f3fb.png","sheet_x":38,"sheet_y":42,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F926-1F3FC","non_qualified":null,"image":"1f926-1f3fc.png","sheet_x":38,"sheet_y":43,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F926-1F3FD","non_qualified":null,"image":"1f926-1f3fd.png","sheet_x":38,"sheet_y":44,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F926-1F3FE","non_qualified":null,"image":"1f926-1f3fe.png","sheet_x":38,"sheet_y":45,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F926-1F3FF","non_qualified":null,"image":"1f926-1f3ff.png","sheet_x":38,"sheet_y":46,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Face Palm","b":"1F926","k":[38,41],"o":9},"flag-sv":{"a":"El Salvador Flag","b":"1F1F8-1F1FB","k":[4,38]},"man-facepalming":{"skin_variations":{"1F3FB":{"unified":"1F926-1F3FB-200D-2642-FE0F","non_qualified":"1F926-1F3FB-200D-2642","image":"1f926-1f3fb-200d-2642-fe0f.png","sheet_x":38,"sheet_y":36,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F926-1F3FC-200D-2642-FE0F","non_qualified":"1F926-1F3FC-200D-2642","image":"1f926-1f3fc-200d-2642-fe0f.png","sheet_x":38,"sheet_y":37,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F926-1F3FD-200D-2642-FE0F","non_qualified":"1F926-1F3FD-200D-2642","image":"1f926-1f3fd-200d-2642-fe0f.png","sheet_x":38,"sheet_y":38,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F926-1F3FE-200D-2642-FE0F","non_qualified":"1F926-1F3FE-200D-2642","image":"1f926-1f3fe-200d-2642-fe0f.png","sheet_x":38,"sheet_y":39,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F926-1F3FF-200D-2642-FE0F","non_qualified":"1F926-1F3FF-200D-2642","image":"1f926-1f3ff-200d-2642-fe0f.png","sheet_x":38,"sheet_y":40,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Facepalming","b":"1F926-200D-2642-FE0F","c":"1F926-200D-2642","k":[38,35],"o":9},"flag-sx":{"a":"Sint Maarten Flag","b":"1F1F8-1F1FD","k":[4,39]},"flag-sy":{"a":"Syria Flag","b":"1F1F8-1F1FE","k":[4,40]},"woman-facepalming":{"skin_variations":{"1F3FB":{"unified":"1F926-1F3FB-200D-2640-FE0F","non_qualified":"1F926-1F3FB-200D-2640","image":"1f926-1f3fb-200d-2640-fe0f.png","sheet_x":38,"sheet_y":30,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F926-1F3FC-200D-2640-FE0F","non_qualified":"1F926-1F3FC-200D-2640","image":"1f926-1f3fc-200d-2640-fe0f.png","sheet_x":38,"sheet_y":31,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F926-1F3FD-200D-2640-FE0F","non_qualified":"1F926-1F3FD-200D-2640","image":"1f926-1f3fd-200d-2640-fe0f.png","sheet_x":38,"sheet_y":32,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F926-1F3FE-200D-2640-FE0F","non_qualified":"1F926-1F3FE-200D-2640","image":"1f926-1f3fe-200d-2640-fe0f.png","sheet_x":38,"sheet_y":33,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F926-1F3FF-200D-2640-FE0F","non_qualified":"1F926-1F3FF-200D-2640","image":"1f926-1f3ff-200d-2640-fe0f.png","sheet_x":38,"sheet_y":34,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Facepalming","b":"1F926-200D-2640-FE0F","c":"1F926-200D-2640","k":[38,29],"o":9},"shrug":{"skin_variations":{"1F3FB":{"unified":"1F937-1F3FB","non_qualified":null,"image":"1f937-1f3fb.png","sheet_x":40,"sheet_y":7,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F937-1F3FC","non_qualified":null,"image":"1f937-1f3fc.png","sheet_x":40,"sheet_y":8,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F937-1F3FD","non_qualified":null,"image":"1f937-1f3fd.png","sheet_x":40,"sheet_y":9,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F937-1F3FE","non_qualified":null,"image":"1f937-1f3fe.png","sheet_x":40,"sheet_y":10,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F937-1F3FF","non_qualified":null,"image":"1f937-1f3ff.png","sheet_x":40,"sheet_y":11,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Shrug","b":"1F937","k":[40,6],"o":9},"flag-sz":{"a":"Swaziland Flag","b":"1F1F8-1F1FF","k":[4,41]},"flag-ta":{"a":"Tristan Da Cunha Flag","b":"1F1F9-1F1E6","k":[4,42]},"man-shrugging":{"skin_variations":{"1F3FB":{"unified":"1F937-1F3FB-200D-2642-FE0F","non_qualified":"1F937-1F3FB-200D-2642","image":"1f937-1f3fb-200d-2642-fe0f.png","sheet_x":40,"sheet_y":1,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F937-1F3FC-200D-2642-FE0F","non_qualified":"1F937-1F3FC-200D-2642","image":"1f937-1f3fc-200d-2642-fe0f.png","sheet_x":40,"sheet_y":2,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F937-1F3FD-200D-2642-FE0F","non_qualified":"1F937-1F3FD-200D-2642","image":"1f937-1f3fd-200d-2642-fe0f.png","sheet_x":40,"sheet_y":3,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F937-1F3FE-200D-2642-FE0F","non_qualified":"1F937-1F3FE-200D-2642","image":"1f937-1f3fe-200d-2642-fe0f.png","sheet_x":40,"sheet_y":4,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F937-1F3FF-200D-2642-FE0F","non_qualified":"1F937-1F3FF-200D-2642","image":"1f937-1f3ff-200d-2642-fe0f.png","sheet_x":40,"sheet_y":5,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Shrugging","b":"1F937-200D-2642-FE0F","c":"1F937-200D-2642","k":[40,0],"o":9},"woman-shrugging":{"skin_variations":{"1F3FB":{"unified":"1F937-1F3FB-200D-2640-FE0F","non_qualified":"1F937-1F3FB-200D-2640","image":"1f937-1f3fb-200d-2640-fe0f.png","sheet_x":39,"sheet_y":47,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F937-1F3FC-200D-2640-FE0F","non_qualified":"1F937-1F3FC-200D-2640","image":"1f937-1f3fc-200d-2640-fe0f.png","sheet_x":39,"sheet_y":48,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F937-1F3FD-200D-2640-FE0F","non_qualified":"1F937-1F3FD-200D-2640","image":"1f937-1f3fd-200d-2640-fe0f.png","sheet_x":39,"sheet_y":49,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F937-1F3FE-200D-2640-FE0F","non_qualified":"1F937-1F3FE-200D-2640","image":"1f937-1f3fe-200d-2640-fe0f.png","sheet_x":39,"sheet_y":50,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F937-1F3FF-200D-2640-FE0F","non_qualified":"1F937-1F3FF-200D-2640","image":"1f937-1f3ff-200d-2640-fe0f.png","sheet_x":39,"sheet_y":51,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Shrugging","b":"1F937-200D-2640-FE0F","c":"1F937-200D-2640","k":[39,46],"o":9},"flag-tc":{"a":"Turks & Caicos Islands Flag","b":"1F1F9-1F1E8","k":[4,43]},"massage":{"skin_variations":{"1F3FB":{"unified":"1F486-1F3FB","non_qualified":null,"image":"1f486-1f3fb.png","sheet_x":24,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F486-1F3FC","non_qualified":null,"image":"1f486-1f3fc.png","sheet_x":24,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F486-1F3FD","non_qualified":null,"image":"1f486-1f3fd.png","sheet_x":24,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F486-1F3FE","non_qualified":null,"image":"1f486-1f3fe.png","sheet_x":24,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F486-1F3FF","non_qualified":null,"image":"1f486-1f3ff.png","sheet_x":24,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F486-200D-2640-FE0F","a":"Face Massage","b":"1F486","k":[24,10]},"flag-td":{"a":"Chad Flag","b":"1F1F9-1F1E9","k":[4,44]},"man-getting-massage":{"skin_variations":{"1F3FB":{"unified":"1F486-1F3FB-200D-2642-FE0F","non_qualified":"1F486-1F3FB-200D-2642","image":"1f486-1f3fb-200d-2642-fe0f.png","sheet_x":24,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F486-1F3FC-200D-2642-FE0F","non_qualified":"1F486-1F3FC-200D-2642","image":"1f486-1f3fc-200d-2642-fe0f.png","sheet_x":24,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F486-1F3FD-200D-2642-FE0F","non_qualified":"1F486-1F3FD-200D-2642","image":"1f486-1f3fd-200d-2642-fe0f.png","sheet_x":24,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F486-1F3FE-200D-2642-FE0F","non_qualified":"1F486-1F3FE-200D-2642","image":"1f486-1f3fe-200d-2642-fe0f.png","sheet_x":24,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F486-1F3FF-200D-2642-FE0F","non_qualified":"1F486-1F3FF-200D-2642","image":"1f486-1f3ff-200d-2642-fe0f.png","sheet_x":24,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Getting Massage","b":"1F486-200D-2642-FE0F","c":"1F486-200D-2642","k":[24,4]},"woman-getting-massage":{"skin_variations":{"1F3FB":{"unified":"1F486-1F3FB-200D-2640-FE0F","non_qualified":"1F486-1F3FB-200D-2640","image":"1f486-1f3fb-200d-2640-fe0f.png","sheet_x":23,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F486-1F3FC-200D-2640-FE0F","non_qualified":"1F486-1F3FC-200D-2640","image":"1f486-1f3fc-200d-2640-fe0f.png","sheet_x":24,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F486-1F3FD-200D-2640-FE0F","non_qualified":"1F486-1F3FD-200D-2640","image":"1f486-1f3fd-200d-2640-fe0f.png","sheet_x":24,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F486-1F3FE-200D-2640-FE0F","non_qualified":"1F486-1F3FE-200D-2640","image":"1f486-1f3fe-200d-2640-fe0f.png","sheet_x":24,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F486-1F3FF-200D-2640-FE0F","non_qualified":"1F486-1F3FF-200D-2640","image":"1f486-1f3ff-200d-2640-fe0f.png","sheet_x":24,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F486","a":"Woman Getting Massage","b":"1F486-200D-2640-FE0F","c":"1F486-200D-2640","k":[23,50]},"flag-tg":{"a":"Togo Flag","b":"1F1F9-1F1EC","k":[4,46]},"haircut":{"skin_variations":{"1F3FB":{"unified":"1F487-1F3FB","non_qualified":null,"image":"1f487-1f3fb.png","sheet_x":24,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F487-1F3FC","non_qualified":null,"image":"1f487-1f3fc.png","sheet_x":24,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F487-1F3FD","non_qualified":null,"image":"1f487-1f3fd.png","sheet_x":24,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F487-1F3FE","non_qualified":null,"image":"1f487-1f3fe.png","sheet_x":24,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F487-1F3FF","non_qualified":null,"image":"1f487-1f3ff.png","sheet_x":24,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F487-200D-2640-FE0F","a":"Haircut","b":"1F487","k":[24,28]},"flag-th":{"a":"Thailand Flag","b":"1F1F9-1F1ED","k":[4,47]},"man-getting-haircut":{"skin_variations":{"1F3FB":{"unified":"1F487-1F3FB-200D-2642-FE0F","non_qualified":"1F487-1F3FB-200D-2642","image":"1f487-1f3fb-200d-2642-fe0f.png","sheet_x":24,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F487-1F3FC-200D-2642-FE0F","non_qualified":"1F487-1F3FC-200D-2642","image":"1f487-1f3fc-200d-2642-fe0f.png","sheet_x":24,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F487-1F3FD-200D-2642-FE0F","non_qualified":"1F487-1F3FD-200D-2642","image":"1f487-1f3fd-200d-2642-fe0f.png","sheet_x":24,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F487-1F3FE-200D-2642-FE0F","non_qualified":"1F487-1F3FE-200D-2642","image":"1f487-1f3fe-200d-2642-fe0f.png","sheet_x":24,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F487-1F3FF-200D-2642-FE0F","non_qualified":"1F487-1F3FF-200D-2642","image":"1f487-1f3ff-200d-2642-fe0f.png","sheet_x":24,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Getting Haircut","b":"1F487-200D-2642-FE0F","c":"1F487-200D-2642","k":[24,22]},"flag-tj":{"a":"Tajikistan Flag","b":"1F1F9-1F1EF","k":[4,48]},"flag-tk":{"a":"Tokelau Flag","b":"1F1F9-1F1F0","k":[4,49]},"woman-getting-haircut":{"skin_variations":{"1F3FB":{"unified":"1F487-1F3FB-200D-2640-FE0F","non_qualified":"1F487-1F3FB-200D-2640","image":"1f487-1f3fb-200d-2640-fe0f.png","sheet_x":24,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F487-1F3FC-200D-2640-FE0F","non_qualified":"1F487-1F3FC-200D-2640","image":"1f487-1f3fc-200d-2640-fe0f.png","sheet_x":24,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F487-1F3FD-200D-2640-FE0F","non_qualified":"1F487-1F3FD-200D-2640","image":"1f487-1f3fd-200d-2640-fe0f.png","sheet_x":24,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F487-1F3FE-200D-2640-FE0F","non_qualified":"1F487-1F3FE-200D-2640","image":"1f487-1f3fe-200d-2640-fe0f.png","sheet_x":24,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F487-1F3FF-200D-2640-FE0F","non_qualified":"1F487-1F3FF-200D-2640","image":"1f487-1f3ff-200d-2640-fe0f.png","sheet_x":24,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F487","a":"Woman Getting Haircut","b":"1F487-200D-2640-FE0F","c":"1F487-200D-2640","k":[24,16]},"walking":{"skin_variations":{"1F3FB":{"unified":"1F6B6-1F3FB","non_qualified":null,"image":"1f6b6-1f3fb.png","sheet_x":36,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F6B6-1F3FC","non_qualified":null,"image":"1f6b6-1f3fc.png","sheet_x":36,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F6B6-1F3FD","non_qualified":null,"image":"1f6b6-1f3fd.png","sheet_x":36,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F6B6-1F3FE","non_qualified":null,"image":"1f6b6-1f3fe.png","sheet_x":36,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F6B6-1F3FF","non_qualified":null,"image":"1f6b6-1f3ff.png","sheet_x":36,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F6B6-200D-2642-FE0F","a":"Pedestrian","b":"1F6B6","k":[36,21]},"flag-tl":{"a":"Timor-Leste Flag","b":"1F1F9-1F1F1","k":[4,50]},"man-walking":{"skin_variations":{"1F3FB":{"unified":"1F6B6-1F3FB-200D-2642-FE0F","non_qualified":"1F6B6-1F3FB-200D-2642","image":"1f6b6-1f3fb-200d-2642-fe0f.png","sheet_x":36,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6B6-1F3FC-200D-2642-FE0F","non_qualified":"1F6B6-1F3FC-200D-2642","image":"1f6b6-1f3fc-200d-2642-fe0f.png","sheet_x":36,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6B6-1F3FD-200D-2642-FE0F","non_qualified":"1F6B6-1F3FD-200D-2642","image":"1f6b6-1f3fd-200d-2642-fe0f.png","sheet_x":36,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6B6-1F3FE-200D-2642-FE0F","non_qualified":"1F6B6-1F3FE-200D-2642","image":"1f6b6-1f3fe-200d-2642-fe0f.png","sheet_x":36,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6B6-1F3FF-200D-2642-FE0F","non_qualified":"1F6B6-1F3FF-200D-2642","image":"1f6b6-1f3ff-200d-2642-fe0f.png","sheet_x":36,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F6B6","a":"Man Walking","b":"1F6B6-200D-2642-FE0F","c":"1F6B6-200D-2642","k":[36,15]},"flag-tm":{"a":"Turkmenistan Flag","b":"1F1F9-1F1F2","k":[4,51]},"woman-walking":{"skin_variations":{"1F3FB":{"unified":"1F6B6-1F3FB-200D-2640-FE0F","non_qualified":"1F6B6-1F3FB-200D-2640","image":"1f6b6-1f3fb-200d-2640-fe0f.png","sheet_x":36,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6B6-1F3FC-200D-2640-FE0F","non_qualified":"1F6B6-1F3FC-200D-2640","image":"1f6b6-1f3fc-200d-2640-fe0f.png","sheet_x":36,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6B6-1F3FD-200D-2640-FE0F","non_qualified":"1F6B6-1F3FD-200D-2640","image":"1f6b6-1f3fd-200d-2640-fe0f.png","sheet_x":36,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6B6-1F3FE-200D-2640-FE0F","non_qualified":"1F6B6-1F3FE-200D-2640","image":"1f6b6-1f3fe-200d-2640-fe0f.png","sheet_x":36,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6B6-1F3FF-200D-2640-FE0F","non_qualified":"1F6B6-1F3FF-200D-2640","image":"1f6b6-1f3ff-200d-2640-fe0f.png","sheet_x":36,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Walking","b":"1F6B6-200D-2640-FE0F","c":"1F6B6-200D-2640","k":[36,9]},"flag-tn":{"a":"Tunisia Flag","b":"1F1F9-1F1F3","k":[5,0]},"runner":{"skin_variations":{"1F3FB":{"unified":"1F3C3-1F3FB","non_qualified":null,"image":"1f3c3-1f3fb.png","sheet_x":9,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F3C3-1F3FC","non_qualified":null,"image":"1f3c3-1f3fc.png","sheet_x":9,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F3C3-1F3FD","non_qualified":null,"image":"1f3c3-1f3fd.png","sheet_x":9,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F3C3-1F3FE","non_qualified":null,"image":"1f3c3-1f3fe.png","sheet_x":9,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F3C3-1F3FF","non_qualified":null,"image":"1f3c3-1f3ff.png","sheet_x":9,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F3C3-200D-2642-FE0F","a":"Runner","b":"1F3C3","k":[9,46],"n":["running"]},"flag-to":{"a":"Tonga Flag","b":"1F1F9-1F1F4","k":[5,1]},"man-running":{"skin_variations":{"1F3FB":{"unified":"1F3C3-1F3FB-200D-2642-FE0F","non_qualified":"1F3C3-1F3FB-200D-2642","image":"1f3c3-1f3fb-200d-2642-fe0f.png","sheet_x":9,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3C3-1F3FC-200D-2642-FE0F","non_qualified":"1F3C3-1F3FC-200D-2642","image":"1f3c3-1f3fc-200d-2642-fe0f.png","sheet_x":9,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3C3-1F3FD-200D-2642-FE0F","non_qualified":"1F3C3-1F3FD-200D-2642","image":"1f3c3-1f3fd-200d-2642-fe0f.png","sheet_x":9,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3C3-1F3FE-200D-2642-FE0F","non_qualified":"1F3C3-1F3FE-200D-2642","image":"1f3c3-1f3fe-200d-2642-fe0f.png","sheet_x":9,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3C3-1F3FF-200D-2642-FE0F","non_qualified":"1F3C3-1F3FF-200D-2642","image":"1f3c3-1f3ff-200d-2642-fe0f.png","sheet_x":9,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F3C3","a":"Man Running","b":"1F3C3-200D-2642-FE0F","c":"1F3C3-200D-2642","k":[9,40]},"flag-tr":{"a":"Turkey Flag","b":"1F1F9-1F1F7","k":[5,2]},"flag-tt":{"a":"Trinidad & Tobago Flag","b":"1F1F9-1F1F9","k":[5,3]},"woman-running":{"skin_variations":{"1F3FB":{"unified":"1F3C3-1F3FB-200D-2640-FE0F","non_qualified":"1F3C3-1F3FB-200D-2640","image":"1f3c3-1f3fb-200d-2640-fe0f.png","sheet_x":9,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3C3-1F3FC-200D-2640-FE0F","non_qualified":"1F3C3-1F3FC-200D-2640","image":"1f3c3-1f3fc-200d-2640-fe0f.png","sheet_x":9,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3C3-1F3FD-200D-2640-FE0F","non_qualified":"1F3C3-1F3FD-200D-2640","image":"1f3c3-1f3fd-200d-2640-fe0f.png","sheet_x":9,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3C3-1F3FE-200D-2640-FE0F","non_qualified":"1F3C3-1F3FE-200D-2640","image":"1f3c3-1f3fe-200d-2640-fe0f.png","sheet_x":9,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3C3-1F3FF-200D-2640-FE0F","non_qualified":"1F3C3-1F3FF-200D-2640","image":"1f3c3-1f3ff-200d-2640-fe0f.png","sheet_x":9,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Running","b":"1F3C3-200D-2640-FE0F","c":"1F3C3-200D-2640","k":[9,34]},"flag-tv":{"a":"Tuvalu Flag","b":"1F1F9-1F1FB","k":[5,4]},"dancer":{"skin_variations":{"1F3FB":{"unified":"1F483-1F3FB","non_qualified":null,"image":"1f483-1f3fb.png","sheet_x":23,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F483-1F3FC","non_qualified":null,"image":"1f483-1f3fc.png","sheet_x":23,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F483-1F3FD","non_qualified":null,"image":"1f483-1f3fd.png","sheet_x":23,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F483-1F3FE","non_qualified":null,"image":"1f483-1f3fe.png","sheet_x":23,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F483-1F3FF","non_qualified":null,"image":"1f483-1f3ff.png","sheet_x":23,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Dancer","b":"1F483","j":["female","girl","woman","fun"],"k":[23,37]},"flag-tw":{"a":"Taiwan Flag","b":"1F1F9-1F1FC","k":[5,5]},"man_dancing":{"skin_variations":{"1F3FB":{"unified":"1F57A-1F3FB","non_qualified":null,"image":"1f57a-1f3fb.png","sheet_x":29,"sheet_y":22,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F57A-1F3FC","non_qualified":null,"image":"1f57a-1f3fc.png","sheet_x":29,"sheet_y":23,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F57A-1F3FD","non_qualified":null,"image":"1f57a-1f3fd.png","sheet_x":29,"sheet_y":24,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F57A-1F3FE","non_qualified":null,"image":"1f57a-1f3fe.png","sheet_x":29,"sheet_y":25,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F57A-1F3FF","non_qualified":null,"image":"1f57a-1f3ff.png","sheet_x":29,"sheet_y":26,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Man Dancing","b":"1F57A","j":["male","boy","fun","dancer"],"k":[29,21],"o":9},"dancers":{"obsoleted_by":"1F46F-200D-2640-FE0F","a":"Woman with Bunny Ears","b":"1F46F","k":[21,1]},"flag-tz":{"a":"Tanzania Flag","b":"1F1F9-1F1FF","k":[5,6]},"flag-ua":{"a":"Ukraine Flag","b":"1F1FA-1F1E6","k":[5,7]},"man-with-bunny-ears-partying":{"a":"Man with Bunny Ears Partying","b":"1F46F-200D-2642-FE0F","c":"1F46F-200D-2642","k":[21,0]},"woman-with-bunny-ears-partying":{"obsoletes":"1F46F","a":"Woman with Bunny Ears Partying","b":"1F46F-200D-2640-FE0F","c":"1F46F-200D-2640","k":[20,51]},"flag-ug":{"a":"Uganda Flag","b":"1F1FA-1F1EC","k":[5,8]},"flag-um":{"a":"U.s. Outlying Islands Flag","b":"1F1FA-1F1F2","k":[5,9]},"person_in_steamy_room":{"skin_variations":{"1F3FB":{"unified":"1F9D6-1F3FB","non_qualified":null,"image":"1f9d6-1f3fb.png","sheet_x":43,"sheet_y":41,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D6-1F3FB-200D-2642-FE0F"},"1F3FC":{"unified":"1F9D6-1F3FC","non_qualified":null,"image":"1f9d6-1f3fc.png","sheet_x":43,"sheet_y":42,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D6-1F3FC-200D-2642-FE0F"},"1F3FD":{"unified":"1F9D6-1F3FD","non_qualified":null,"image":"1f9d6-1f3fd.png","sheet_x":43,"sheet_y":43,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D6-1F3FD-200D-2642-FE0F"},"1F3FE":{"unified":"1F9D6-1F3FE","non_qualified":null,"image":"1f9d6-1f3fe.png","sheet_x":43,"sheet_y":44,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D6-1F3FE-200D-2642-FE0F"},"1F3FF":{"unified":"1F9D6-1F3FF","non_qualified":null,"image":"1f9d6-1f3ff.png","sheet_x":43,"sheet_y":45,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D6-1F3FF-200D-2642-FE0F"}},"obsoleted_by":"1F9D6-200D-2642-FE0F","a":"Person in Steamy Room","b":"1F9D6","k":[43,40],"o":10},"woman_in_steamy_room":{"skin_variations":{"1F3FB":{"unified":"1F9D6-1F3FB-200D-2640-FE0F","non_qualified":"1F9D6-1F3FB-200D-2640","image":"1f9d6-1f3fb-200d-2640-fe0f.png","sheet_x":43,"sheet_y":29,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9D6-1F3FC-200D-2640-FE0F","non_qualified":"1F9D6-1F3FC-200D-2640","image":"1f9d6-1f3fc-200d-2640-fe0f.png","sheet_x":43,"sheet_y":30,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9D6-1F3FD-200D-2640-FE0F","non_qualified":"1F9D6-1F3FD-200D-2640","image":"1f9d6-1f3fd-200d-2640-fe0f.png","sheet_x":43,"sheet_y":31,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9D6-1F3FE-200D-2640-FE0F","non_qualified":"1F9D6-1F3FE-200D-2640","image":"1f9d6-1f3fe-200d-2640-fe0f.png","sheet_x":43,"sheet_y":32,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9D6-1F3FF-200D-2640-FE0F","non_qualified":"1F9D6-1F3FF-200D-2640","image":"1f9d6-1f3ff-200d-2640-fe0f.png","sheet_x":43,"sheet_y":33,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman in Steamy Room","b":"1F9D6-200D-2640-FE0F","c":"1F9D6-200D-2640","k":[43,28],"o":10},"flag-un":{"a":"United Nations Flag","b":"1F1FA-1F1F3","k":[5,10]},"us":{"a":"United States Flag","b":"1F1FA-1F1F8","j":["united","states","america","flag","nation","country","banner"],"k":[5,11],"n":["flag-us"]},"man_in_steamy_room":{"skin_variations":{"1F3FB":{"unified":"1F9D6-1F3FB-200D-2642-FE0F","non_qualified":"1F9D6-1F3FB-200D-2642","image":"1f9d6-1f3fb-200d-2642-fe0f.png","sheet_x":43,"sheet_y":35,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D6-1F3FB"},"1F3FC":{"unified":"1F9D6-1F3FC-200D-2642-FE0F","non_qualified":"1F9D6-1F3FC-200D-2642","image":"1f9d6-1f3fc-200d-2642-fe0f.png","sheet_x":43,"sheet_y":36,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D6-1F3FC"},"1F3FD":{"unified":"1F9D6-1F3FD-200D-2642-FE0F","non_qualified":"1F9D6-1F3FD-200D-2642","image":"1f9d6-1f3fd-200d-2642-fe0f.png","sheet_x":43,"sheet_y":37,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D6-1F3FD"},"1F3FE":{"unified":"1F9D6-1F3FE-200D-2642-FE0F","non_qualified":"1F9D6-1F3FE-200D-2642","image":"1f9d6-1f3fe-200d-2642-fe0f.png","sheet_x":43,"sheet_y":38,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D6-1F3FE"},"1F3FF":{"unified":"1F9D6-1F3FF-200D-2642-FE0F","non_qualified":"1F9D6-1F3FF-200D-2642","image":"1f9d6-1f3ff-200d-2642-fe0f.png","sheet_x":43,"sheet_y":39,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D6-1F3FF"}},"obsoletes":"1F9D6","a":"Man in Steamy Room","b":"1F9D6-200D-2642-FE0F","c":"1F9D6-200D-2642","k":[43,34],"o":10},"person_climbing":{"skin_variations":{"1F3FB":{"unified":"1F9D7-1F3FB","non_qualified":null,"image":"1f9d7-1f3fb.png","sheet_x":44,"sheet_y":7,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D7-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9D7-1F3FC","non_qualified":null,"image":"1f9d7-1f3fc.png","sheet_x":44,"sheet_y":8,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D7-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9D7-1F3FD","non_qualified":null,"image":"1f9d7-1f3fd.png","sheet_x":44,"sheet_y":9,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D7-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9D7-1F3FE","non_qualified":null,"image":"1f9d7-1f3fe.png","sheet_x":44,"sheet_y":10,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D7-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9D7-1F3FF","non_qualified":null,"image":"1f9d7-1f3ff.png","sheet_x":44,"sheet_y":11,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D7-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9D7-200D-2640-FE0F","a":"Person Climbing","b":"1F9D7","k":[44,6],"o":10},"flag-uy":{"a":"Uruguay Flag","b":"1F1FA-1F1FE","k":[5,12]},"woman_climbing":{"skin_variations":{"1F3FB":{"unified":"1F9D7-1F3FB-200D-2640-FE0F","non_qualified":"1F9D7-1F3FB-200D-2640","image":"1f9d7-1f3fb-200d-2640-fe0f.png","sheet_x":43,"sheet_y":47,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D7-1F3FB"},"1F3FC":{"unified":"1F9D7-1F3FC-200D-2640-FE0F","non_qualified":"1F9D7-1F3FC-200D-2640","image":"1f9d7-1f3fc-200d-2640-fe0f.png","sheet_x":43,"sheet_y":48,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D7-1F3FC"},"1F3FD":{"unified":"1F9D7-1F3FD-200D-2640-FE0F","non_qualified":"1F9D7-1F3FD-200D-2640","image":"1f9d7-1f3fd-200d-2640-fe0f.png","sheet_x":43,"sheet_y":49,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D7-1F3FD"},"1F3FE":{"unified":"1F9D7-1F3FE-200D-2640-FE0F","non_qualified":"1F9D7-1F3FE-200D-2640","image":"1f9d7-1f3fe-200d-2640-fe0f.png","sheet_x":43,"sheet_y":50,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D7-1F3FE"},"1F3FF":{"unified":"1F9D7-1F3FF-200D-2640-FE0F","non_qualified":"1F9D7-1F3FF-200D-2640","image":"1f9d7-1f3ff-200d-2640-fe0f.png","sheet_x":43,"sheet_y":51,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D7-1F3FF"}},"obsoletes":"1F9D7","a":"Woman Climbing","b":"1F9D7-200D-2640-FE0F","c":"1F9D7-200D-2640","k":[43,46],"o":10},"flag-uz":{"a":"Uzbekistan Flag","b":"1F1FA-1F1FF","k":[5,13]},"man_climbing":{"skin_variations":{"1F3FB":{"unified":"1F9D7-1F3FB-200D-2642-FE0F","non_qualified":"1F9D7-1F3FB-200D-2642","image":"1f9d7-1f3fb-200d-2642-fe0f.png","sheet_x":44,"sheet_y":1,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9D7-1F3FC-200D-2642-FE0F","non_qualified":"1F9D7-1F3FC-200D-2642","image":"1f9d7-1f3fc-200d-2642-fe0f.png","sheet_x":44,"sheet_y":2,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9D7-1F3FD-200D-2642-FE0F","non_qualified":"1F9D7-1F3FD-200D-2642","image":"1f9d7-1f3fd-200d-2642-fe0f.png","sheet_x":44,"sheet_y":3,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9D7-1F3FE-200D-2642-FE0F","non_qualified":"1F9D7-1F3FE-200D-2642","image":"1f9d7-1f3fe-200d-2642-fe0f.png","sheet_x":44,"sheet_y":4,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9D7-1F3FF-200D-2642-FE0F","non_qualified":"1F9D7-1F3FF-200D-2642","image":"1f9d7-1f3ff-200d-2642-fe0f.png","sheet_x":44,"sheet_y":5,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Climbing","b":"1F9D7-200D-2642-FE0F","c":"1F9D7-200D-2642","k":[44,0],"o":10},"flag-va":{"a":"Vatican City Flag","b":"1F1FB-1F1E6","k":[5,14]},"person_in_lotus_position":{"skin_variations":{"1F3FB":{"unified":"1F9D8-1F3FB","non_qualified":null,"image":"1f9d8-1f3fb.png","sheet_x":44,"sheet_y":25,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D8-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9D8-1F3FC","non_qualified":null,"image":"1f9d8-1f3fc.png","sheet_x":44,"sheet_y":26,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D8-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9D8-1F3FD","non_qualified":null,"image":"1f9d8-1f3fd.png","sheet_x":44,"sheet_y":27,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D8-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9D8-1F3FE","non_qualified":null,"image":"1f9d8-1f3fe.png","sheet_x":44,"sheet_y":28,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D8-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9D8-1F3FF","non_qualified":null,"image":"1f9d8-1f3ff.png","sheet_x":44,"sheet_y":29,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D8-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9D8-200D-2640-FE0F","a":"Person in Lotus Position","b":"1F9D8","k":[44,24],"o":10},"flag-vc":{"a":"St. Vincent & Grenadines Flag","b":"1F1FB-1F1E8","k":[5,15]},"flag-ve":{"a":"Venezuela Flag","b":"1F1FB-1F1EA","k":[5,16]},"woman_in_lotus_position":{"skin_variations":{"1F3FB":{"unified":"1F9D8-1F3FB-200D-2640-FE0F","non_qualified":"1F9D8-1F3FB-200D-2640","image":"1f9d8-1f3fb-200d-2640-fe0f.png","sheet_x":44,"sheet_y":13,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D8-1F3FB"},"1F3FC":{"unified":"1F9D8-1F3FC-200D-2640-FE0F","non_qualified":"1F9D8-1F3FC-200D-2640","image":"1f9d8-1f3fc-200d-2640-fe0f.png","sheet_x":44,"sheet_y":14,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D8-1F3FC"},"1F3FD":{"unified":"1F9D8-1F3FD-200D-2640-FE0F","non_qualified":"1F9D8-1F3FD-200D-2640","image":"1f9d8-1f3fd-200d-2640-fe0f.png","sheet_x":44,"sheet_y":15,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D8-1F3FD"},"1F3FE":{"unified":"1F9D8-1F3FE-200D-2640-FE0F","non_qualified":"1F9D8-1F3FE-200D-2640","image":"1f9d8-1f3fe-200d-2640-fe0f.png","sheet_x":44,"sheet_y":16,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D8-1F3FE"},"1F3FF":{"unified":"1F9D8-1F3FF-200D-2640-FE0F","non_qualified":"1F9D8-1F3FF-200D-2640","image":"1f9d8-1f3ff-200d-2640-fe0f.png","sheet_x":44,"sheet_y":17,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D8-1F3FF"}},"obsoletes":"1F9D8","a":"Woman in Lotus Position","b":"1F9D8-200D-2640-FE0F","c":"1F9D8-200D-2640","k":[44,12],"o":10},"man_in_lotus_position":{"skin_variations":{"1F3FB":{"unified":"1F9D8-1F3FB-200D-2642-FE0F","non_qualified":"1F9D8-1F3FB-200D-2642","image":"1f9d8-1f3fb-200d-2642-fe0f.png","sheet_x":44,"sheet_y":19,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9D8-1F3FC-200D-2642-FE0F","non_qualified":"1F9D8-1F3FC-200D-2642","image":"1f9d8-1f3fc-200d-2642-fe0f.png","sheet_x":44,"sheet_y":20,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9D8-1F3FD-200D-2642-FE0F","non_qualified":"1F9D8-1F3FD-200D-2642","image":"1f9d8-1f3fd-200d-2642-fe0f.png","sheet_x":44,"sheet_y":21,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9D8-1F3FE-200D-2642-FE0F","non_qualified":"1F9D8-1F3FE-200D-2642","image":"1f9d8-1f3fe-200d-2642-fe0f.png","sheet_x":44,"sheet_y":22,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9D8-1F3FF-200D-2642-FE0F","non_qualified":"1F9D8-1F3FF-200D-2642","image":"1f9d8-1f3ff-200d-2642-fe0f.png","sheet_x":44,"sheet_y":23,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man in Lotus Position","b":"1F9D8-200D-2642-FE0F","c":"1F9D8-200D-2642","k":[44,18],"o":10},"flag-vg":{"a":"British Virgin Islands Flag","b":"1F1FB-1F1EC","k":[5,17]},"flag-vi":{"a":"U.s. Virgin Islands Flag","b":"1F1FB-1F1EE","k":[5,18]},"bath":{"skin_variations":{"1F3FB":{"unified":"1F6C0-1F3FB","non_qualified":null,"image":"1f6c0-1f3fb.png","sheet_x":36,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F6C0-1F3FC","non_qualified":null,"image":"1f6c0-1f3fc.png","sheet_x":36,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F6C0-1F3FD","non_qualified":null,"image":"1f6c0-1f3fd.png","sheet_x":36,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F6C0-1F3FE","non_qualified":null,"image":"1f6c0-1f3fe.png","sheet_x":36,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F6C0-1F3FF","non_qualified":null,"image":"1f6c0-1f3ff.png","sheet_x":36,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Bath","b":"1F6C0","j":["clean","shower","bathroom"],"k":[36,36]},"sleeping_accommodation":{"skin_variations":{"1F3FB":{"unified":"1F6CC-1F3FB","non_qualified":null,"image":"1f6cc-1f3fb.png","sheet_x":36,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F6CC-1F3FC","non_qualified":null,"image":"1f6cc-1f3fc.png","sheet_x":36,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F6CC-1F3FD","non_qualified":null,"image":"1f6cc-1f3fd.png","sheet_x":36,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F6CC-1F3FE","non_qualified":null,"image":"1f6cc-1f3fe.png","sheet_x":37,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F6CC-1F3FF","non_qualified":null,"image":"1f6cc-1f3ff.png","sheet_x":37,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Sleeping Accommodation","b":"1F6CC","k":[36,48],"o":7},"flag-vn":{"a":"Vietnam Flag","b":"1F1FB-1F1F3","k":[5,19]},"man_in_business_suit_levitating":{"skin_variations":{"1F3FB":{"unified":"1F574-1F3FB","non_qualified":null,"image":"1f574-1f3fb.png","sheet_x":28,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F574-1F3FC","non_qualified":null,"image":"1f574-1f3fc.png","sheet_x":28,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F574-1F3FD","non_qualified":null,"image":"1f574-1f3fd.png","sheet_x":28,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F574-1F3FE","non_qualified":null,"image":"1f574-1f3fe.png","sheet_x":28,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F574-1F3FF","non_qualified":null,"image":"1f574-1f3ff.png","sheet_x":28,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Man in Business Suit Levitating","b":"1F574-FE0F","c":"1F574","k":[28,45],"o":7},"flag-vu":{"a":"Vanuatu Flag","b":"1F1FB-1F1FA","k":[5,20]},"speaking_head_in_silhouette":{"a":"Speaking Head in Silhouette","b":"1F5E3-FE0F","c":"1F5E3","k":[30,14],"o":7},"bust_in_silhouette":{"a":"Bust in Silhouette","b":"1F464","j":["user","person","human"],"k":[15,40]},"flag-ws":{"a":"Samoa Flag","b":"1F1FC-1F1F8","k":[5,22]},"busts_in_silhouette":{"a":"Busts in Silhouette","b":"1F465","j":["user","person","human","group","team"],"k":[15,41]},"fencer":{"a":"Fencer","b":"1F93A","k":[40,48],"o":9},"flag-ye":{"a":"Yemen Flag","b":"1F1FE-1F1EA","k":[5,24]},"horse_racing":{"skin_variations":{"1F3FB":{"unified":"1F3C7-1F3FB","non_qualified":null,"image":"1f3c7-1f3fb.png","sheet_x":10,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F3C7-1F3FC","non_qualified":null,"image":"1f3c7-1f3fc.png","sheet_x":10,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F3C7-1F3FD","non_qualified":null,"image":"1f3c7-1f3fd.png","sheet_x":10,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F3C7-1F3FE","non_qualified":null,"image":"1f3c7-1f3fe.png","sheet_x":10,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F3C7-1F3FF","non_qualified":null,"image":"1f3c7-1f3ff.png","sheet_x":10,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Horse Racing","b":"1F3C7","j":["animal","betting","competition","gambling","luck"],"k":[10,20]},"flag-za":{"a":"South Africa Flag","b":"1F1FF-1F1E6","k":[5,26]},"skier":{"a":"Skier","b":"26F7-FE0F","c":"26F7","j":["sports","winter","snow"],"k":[48,44],"o":5},"flag-zm":{"a":"Zambia Flag","b":"1F1FF-1F1F2","k":[5,27]},"snowboarder":{"skin_variations":{"1F3FB":{"unified":"1F3C2-1F3FB","non_qualified":null,"image":"1f3c2-1f3fb.png","sheet_x":9,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F3C2-1F3FC","non_qualified":null,"image":"1f3c2-1f3fc.png","sheet_x":9,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F3C2-1F3FD","non_qualified":null,"image":"1f3c2-1f3fd.png","sheet_x":9,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F3C2-1F3FE","non_qualified":null,"image":"1f3c2-1f3fe.png","sheet_x":9,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F3C2-1F3FF","non_qualified":null,"image":"1f3c2-1f3ff.png","sheet_x":9,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Snowboarder","b":"1F3C2","j":["sports","winter"],"k":[9,28]},"golfer":{"skin_variations":{"1F3FB":{"unified":"1F3CC-1F3FB","non_qualified":null,"image":"1f3cc-1f3fb.png","sheet_x":11,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CC-1F3FC","non_qualified":null,"image":"1f3cc-1f3fc.png","sheet_x":11,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CC-1F3FD","non_qualified":null,"image":"1f3cc-1f3fd.png","sheet_x":11,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CC-1F3FE","non_qualified":null,"image":"1f3cc-1f3fe.png","sheet_x":11,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CC-1F3FF","non_qualified":null,"image":"1f3cc-1f3ff.png","sheet_x":11,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoleted_by":"1F3CC-FE0F-200D-2642-FE0F","a":"Golfer","b":"1F3CC-FE0F","c":"1F3CC","k":[11,24],"o":7},"flag-zw":{"a":"Zimbabwe Flag","b":"1F1FF-1F1FC","k":[5,28]},"man-golfing":{"skin_variations":{"1F3FB":{"unified":"1F3CC-1F3FB-200D-2642-FE0F","non_qualified":"1F3CC-1F3FB-200D-2642","image":"1f3cc-1f3fb-200d-2642-fe0f.png","sheet_x":11,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CC-1F3FC-200D-2642-FE0F","non_qualified":"1F3CC-1F3FC-200D-2642","image":"1f3cc-1f3fc-200d-2642-fe0f.png","sheet_x":11,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CC-1F3FD-200D-2642-FE0F","non_qualified":"1F3CC-1F3FD-200D-2642","image":"1f3cc-1f3fd-200d-2642-fe0f.png","sheet_x":11,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CC-1F3FE-200D-2642-FE0F","non_qualified":"1F3CC-1F3FE-200D-2642","image":"1f3cc-1f3fe-200d-2642-fe0f.png","sheet_x":11,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CC-1F3FF-200D-2642-FE0F","non_qualified":"1F3CC-1F3FF-200D-2642","image":"1f3cc-1f3ff-200d-2642-fe0f.png","sheet_x":11,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F3CC-FE0F","a":"Man Golfing","b":"1F3CC-FE0F-200D-2642-FE0F","k":[11,18],"o":7},"flag-england":{"a":"England Flag","b":"1F3F4-E0067-E0062-E0065-E006E-E0067-E007F","k":[12,16],"o":7},"woman-golfing":{"skin_variations":{"1F3FB":{"unified":"1F3CC-1F3FB-200D-2640-FE0F","non_qualified":"1F3CC-1F3FB-200D-2640","image":"1f3cc-1f3fb-200d-2640-fe0f.png","sheet_x":11,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CC-1F3FC-200D-2640-FE0F","non_qualified":"1F3CC-1F3FC-200D-2640","image":"1f3cc-1f3fc-200d-2640-fe0f.png","sheet_x":11,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CC-1F3FD-200D-2640-FE0F","non_qualified":"1F3CC-1F3FD-200D-2640","image":"1f3cc-1f3fd-200d-2640-fe0f.png","sheet_x":11,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CC-1F3FE-200D-2640-FE0F","non_qualified":"1F3CC-1F3FE-200D-2640","image":"1f3cc-1f3fe-200d-2640-fe0f.png","sheet_x":11,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CC-1F3FF-200D-2640-FE0F","non_qualified":"1F3CC-1F3FF-200D-2640","image":"1f3cc-1f3ff-200d-2640-fe0f.png","sheet_x":11,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Golfing","b":"1F3CC-FE0F-200D-2640-FE0F","k":[11,12],"o":7},"flag-scotland":{"a":"Scotland Flag","b":"1F3F4-E0067-E0062-E0073-E0063-E0074-E007F","k":[12,17],"o":7},"flag-wales":{"a":"Wales Flag","b":"1F3F4-E0067-E0062-E0077-E006C-E0073-E007F","k":[12,18],"o":7},"surfer":{"skin_variations":{"1F3FB":{"unified":"1F3C4-1F3FB","non_qualified":null,"image":"1f3c4-1f3fb.png","sheet_x":10,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F3C4-1F3FC","non_qualified":null,"image":"1f3c4-1f3fc.png","sheet_x":10,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F3C4-1F3FD","non_qualified":null,"image":"1f3c4-1f3fd.png","sheet_x":10,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F3C4-1F3FE","non_qualified":null,"image":"1f3c4-1f3fe.png","sheet_x":10,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F3C4-1F3FF","non_qualified":null,"image":"1f3c4-1f3ff.png","sheet_x":10,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F3C4-200D-2642-FE0F","a":"Surfer","b":"1F3C4","k":[10,12]},"man-surfing":{"skin_variations":{"1F3FB":{"unified":"1F3C4-1F3FB-200D-2642-FE0F","non_qualified":"1F3C4-1F3FB-200D-2642","image":"1f3c4-1f3fb-200d-2642-fe0f.png","sheet_x":10,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3C4-1F3FC-200D-2642-FE0F","non_qualified":"1F3C4-1F3FC-200D-2642","image":"1f3c4-1f3fc-200d-2642-fe0f.png","sheet_x":10,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3C4-1F3FD-200D-2642-FE0F","non_qualified":"1F3C4-1F3FD-200D-2642","image":"1f3c4-1f3fd-200d-2642-fe0f.png","sheet_x":10,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3C4-1F3FE-200D-2642-FE0F","non_qualified":"1F3C4-1F3FE-200D-2642","image":"1f3c4-1f3fe-200d-2642-fe0f.png","sheet_x":10,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3C4-1F3FF-200D-2642-FE0F","non_qualified":"1F3C4-1F3FF-200D-2642","image":"1f3c4-1f3ff-200d-2642-fe0f.png","sheet_x":10,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F3C4","a":"Man Surfing","b":"1F3C4-200D-2642-FE0F","c":"1F3C4-200D-2642","k":[10,6]},"woman-surfing":{"skin_variations":{"1F3FB":{"unified":"1F3C4-1F3FB-200D-2640-FE0F","non_qualified":"1F3C4-1F3FB-200D-2640","image":"1f3c4-1f3fb-200d-2640-fe0f.png","sheet_x":10,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3C4-1F3FC-200D-2640-FE0F","non_qualified":"1F3C4-1F3FC-200D-2640","image":"1f3c4-1f3fc-200d-2640-fe0f.png","sheet_x":10,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3C4-1F3FD-200D-2640-FE0F","non_qualified":"1F3C4-1F3FD-200D-2640","image":"1f3c4-1f3fd-200d-2640-fe0f.png","sheet_x":10,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3C4-1F3FE-200D-2640-FE0F","non_qualified":"1F3C4-1F3FE-200D-2640","image":"1f3c4-1f3fe-200d-2640-fe0f.png","sheet_x":10,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3C4-1F3FF-200D-2640-FE0F","non_qualified":"1F3C4-1F3FF-200D-2640","image":"1f3c4-1f3ff-200d-2640-fe0f.png","sheet_x":10,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Surfing","b":"1F3C4-200D-2640-FE0F","c":"1F3C4-200D-2640","k":[10,0]},"rowboat":{"skin_variations":{"1F3FB":{"unified":"1F6A3-1F3FB","non_qualified":null,"image":"1f6a3-1f3fb.png","sheet_x":35,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6A3-1F3FC","non_qualified":null,"image":"1f6a3-1f3fc.png","sheet_x":35,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6A3-1F3FD","non_qualified":null,"image":"1f6a3-1f3fd.png","sheet_x":35,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6A3-1F3FE","non_qualified":null,"image":"1f6a3-1f3fe.png","sheet_x":35,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6A3-1F3FF","non_qualified":null,"image":"1f6a3-1f3ff.png","sheet_x":35,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoleted_by":"1F6A3-200D-2642-FE0F","a":"Rowboat","b":"1F6A3","k":[35,3]},"man-rowing-boat":{"skin_variations":{"1F3FB":{"unified":"1F6A3-1F3FB-200D-2642-FE0F","non_qualified":"1F6A3-1F3FB-200D-2642","image":"1f6a3-1f3fb-200d-2642-fe0f.png","sheet_x":34,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6A3-1F3FC-200D-2642-FE0F","non_qualified":"1F6A3-1F3FC-200D-2642","image":"1f6a3-1f3fc-200d-2642-fe0f.png","sheet_x":34,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6A3-1F3FD-200D-2642-FE0F","non_qualified":"1F6A3-1F3FD-200D-2642","image":"1f6a3-1f3fd-200d-2642-fe0f.png","sheet_x":35,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6A3-1F3FE-200D-2642-FE0F","non_qualified":"1F6A3-1F3FE-200D-2642","image":"1f6a3-1f3fe-200d-2642-fe0f.png","sheet_x":35,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6A3-1F3FF-200D-2642-FE0F","non_qualified":"1F6A3-1F3FF-200D-2642","image":"1f6a3-1f3ff-200d-2642-fe0f.png","sheet_x":35,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F6A3","a":"Man Rowing Boat","b":"1F6A3-200D-2642-FE0F","c":"1F6A3-200D-2642","k":[34,49]},"woman-rowing-boat":{"skin_variations":{"1F3FB":{"unified":"1F6A3-1F3FB-200D-2640-FE0F","non_qualified":"1F6A3-1F3FB-200D-2640","image":"1f6a3-1f3fb-200d-2640-fe0f.png","sheet_x":34,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6A3-1F3FC-200D-2640-FE0F","non_qualified":"1F6A3-1F3FC-200D-2640","image":"1f6a3-1f3fc-200d-2640-fe0f.png","sheet_x":34,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6A3-1F3FD-200D-2640-FE0F","non_qualified":"1F6A3-1F3FD-200D-2640","image":"1f6a3-1f3fd-200d-2640-fe0f.png","sheet_x":34,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6A3-1F3FE-200D-2640-FE0F","non_qualified":"1F6A3-1F3FE-200D-2640","image":"1f6a3-1f3fe-200d-2640-fe0f.png","sheet_x":34,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6A3-1F3FF-200D-2640-FE0F","non_qualified":"1F6A3-1F3FF-200D-2640","image":"1f6a3-1f3ff-200d-2640-fe0f.png","sheet_x":34,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Rowing Boat","b":"1F6A3-200D-2640-FE0F","c":"1F6A3-200D-2640","k":[34,43]},"swimmer":{"skin_variations":{"1F3FB":{"unified":"1F3CA-1F3FB","non_qualified":null,"image":"1f3ca-1f3fb.png","sheet_x":10,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F3CA-1F3FC","non_qualified":null,"image":"1f3ca-1f3fc.png","sheet_x":10,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F3CA-1F3FD","non_qualified":null,"image":"1f3ca-1f3fd.png","sheet_x":10,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F3CA-1F3FE","non_qualified":null,"image":"1f3ca-1f3fe.png","sheet_x":10,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F3CA-1F3FF","non_qualified":null,"image":"1f3ca-1f3ff.png","sheet_x":10,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F3CA-200D-2642-FE0F","a":"Swimmer","b":"1F3CA","k":[10,40]},"man-swimming":{"skin_variations":{"1F3FB":{"unified":"1F3CA-1F3FB-200D-2642-FE0F","non_qualified":"1F3CA-1F3FB-200D-2642","image":"1f3ca-1f3fb-200d-2642-fe0f.png","sheet_x":10,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CA-1F3FC-200D-2642-FE0F","non_qualified":"1F3CA-1F3FC-200D-2642","image":"1f3ca-1f3fc-200d-2642-fe0f.png","sheet_x":10,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CA-1F3FD-200D-2642-FE0F","non_qualified":"1F3CA-1F3FD-200D-2642","image":"1f3ca-1f3fd-200d-2642-fe0f.png","sheet_x":10,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CA-1F3FE-200D-2642-FE0F","non_qualified":"1F3CA-1F3FE-200D-2642","image":"1f3ca-1f3fe-200d-2642-fe0f.png","sheet_x":10,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CA-1F3FF-200D-2642-FE0F","non_qualified":"1F3CA-1F3FF-200D-2642","image":"1f3ca-1f3ff-200d-2642-fe0f.png","sheet_x":10,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F3CA","a":"Man Swimming","b":"1F3CA-200D-2642-FE0F","c":"1F3CA-200D-2642","k":[10,34]},"woman-swimming":{"skin_variations":{"1F3FB":{"unified":"1F3CA-1F3FB-200D-2640-FE0F","non_qualified":"1F3CA-1F3FB-200D-2640","image":"1f3ca-1f3fb-200d-2640-fe0f.png","sheet_x":10,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CA-1F3FC-200D-2640-FE0F","non_qualified":"1F3CA-1F3FC-200D-2640","image":"1f3ca-1f3fc-200d-2640-fe0f.png","sheet_x":10,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CA-1F3FD-200D-2640-FE0F","non_qualified":"1F3CA-1F3FD-200D-2640","image":"1f3ca-1f3fd-200d-2640-fe0f.png","sheet_x":10,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CA-1F3FE-200D-2640-FE0F","non_qualified":"1F3CA-1F3FE-200D-2640","image":"1f3ca-1f3fe-200d-2640-fe0f.png","sheet_x":10,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CA-1F3FF-200D-2640-FE0F","non_qualified":"1F3CA-1F3FF-200D-2640","image":"1f3ca-1f3ff-200d-2640-fe0f.png","sheet_x":10,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Swimming","b":"1F3CA-200D-2640-FE0F","c":"1F3CA-200D-2640","k":[10,28]},"person_with_ball":{"skin_variations":{"1F3FB":{"unified":"26F9-1F3FB","non_qualified":null,"image":"26f9-1f3fb.png","sheet_x":49,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"26F9-1F3FC","non_qualified":null,"image":"26f9-1f3fc.png","sheet_x":49,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"26F9-1F3FD","non_qualified":null,"image":"26f9-1f3fd.png","sheet_x":49,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"26F9-1F3FE","non_qualified":null,"image":"26f9-1f3fe.png","sheet_x":49,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"26F9-1F3FF","non_qualified":null,"image":"26f9-1f3ff.png","sheet_x":49,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoleted_by":"26F9-FE0F-200D-2642-FE0F","a":"Person with Ball","b":"26F9-FE0F","c":"26F9","k":[49,6],"o":5},"man-bouncing-ball":{"skin_variations":{"1F3FB":{"unified":"26F9-1F3FB-200D-2642-FE0F","non_qualified":"26F9-1F3FB-200D-2642","image":"26f9-1f3fb-200d-2642-fe0f.png","sheet_x":49,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"26F9-1F3FC-200D-2642-FE0F","non_qualified":"26F9-1F3FC-200D-2642","image":"26f9-1f3fc-200d-2642-fe0f.png","sheet_x":49,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"26F9-1F3FD-200D-2642-FE0F","non_qualified":"26F9-1F3FD-200D-2642","image":"26f9-1f3fd-200d-2642-fe0f.png","sheet_x":49,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"26F9-1F3FE-200D-2642-FE0F","non_qualified":"26F9-1F3FE-200D-2642","image":"26f9-1f3fe-200d-2642-fe0f.png","sheet_x":49,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"26F9-1F3FF-200D-2642-FE0F","non_qualified":"26F9-1F3FF-200D-2642","image":"26f9-1f3ff-200d-2642-fe0f.png","sheet_x":49,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"26F9-FE0F","a":"Man Bouncing Ball","b":"26F9-FE0F-200D-2642-FE0F","k":[49,0],"o":5},"woman-bouncing-ball":{"skin_variations":{"1F3FB":{"unified":"26F9-1F3FB-200D-2640-FE0F","non_qualified":"26F9-1F3FB-200D-2640","image":"26f9-1f3fb-200d-2640-fe0f.png","sheet_x":48,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"26F9-1F3FC-200D-2640-FE0F","non_qualified":"26F9-1F3FC-200D-2640","image":"26f9-1f3fc-200d-2640-fe0f.png","sheet_x":48,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"26F9-1F3FD-200D-2640-FE0F","non_qualified":"26F9-1F3FD-200D-2640","image":"26f9-1f3fd-200d-2640-fe0f.png","sheet_x":48,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"26F9-1F3FE-200D-2640-FE0F","non_qualified":"26F9-1F3FE-200D-2640","image":"26f9-1f3fe-200d-2640-fe0f.png","sheet_x":48,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"26F9-1F3FF-200D-2640-FE0F","non_qualified":"26F9-1F3FF-200D-2640","image":"26f9-1f3ff-200d-2640-fe0f.png","sheet_x":48,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Bouncing Ball","b":"26F9-FE0F-200D-2640-FE0F","k":[48,46],"o":5},"weight_lifter":{"skin_variations":{"1F3FB":{"unified":"1F3CB-1F3FB","non_qualified":null,"image":"1f3cb-1f3fb.png","sheet_x":11,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CB-1F3FC","non_qualified":null,"image":"1f3cb-1f3fc.png","sheet_x":11,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CB-1F3FD","non_qualified":null,"image":"1f3cb-1f3fd.png","sheet_x":11,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CB-1F3FE","non_qualified":null,"image":"1f3cb-1f3fe.png","sheet_x":11,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CB-1F3FF","non_qualified":null,"image":"1f3cb-1f3ff.png","sheet_x":11,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoleted_by":"1F3CB-FE0F-200D-2642-FE0F","a":"Weight Lifter","b":"1F3CB-FE0F","c":"1F3CB","k":[11,6],"o":7},"man-lifting-weights":{"skin_variations":{"1F3FB":{"unified":"1F3CB-1F3FB-200D-2642-FE0F","non_qualified":"1F3CB-1F3FB-200D-2642","image":"1f3cb-1f3fb-200d-2642-fe0f.png","sheet_x":11,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CB-1F3FC-200D-2642-FE0F","non_qualified":"1F3CB-1F3FC-200D-2642","image":"1f3cb-1f3fc-200d-2642-fe0f.png","sheet_x":11,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CB-1F3FD-200D-2642-FE0F","non_qualified":"1F3CB-1F3FD-200D-2642","image":"1f3cb-1f3fd-200d-2642-fe0f.png","sheet_x":11,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CB-1F3FE-200D-2642-FE0F","non_qualified":"1F3CB-1F3FE-200D-2642","image":"1f3cb-1f3fe-200d-2642-fe0f.png","sheet_x":11,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CB-1F3FF-200D-2642-FE0F","non_qualified":"1F3CB-1F3FF-200D-2642","image":"1f3cb-1f3ff-200d-2642-fe0f.png","sheet_x":11,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F3CB-FE0F","a":"Man Lifting Weights","b":"1F3CB-FE0F-200D-2642-FE0F","k":[11,0],"o":7},"woman-lifting-weights":{"skin_variations":{"1F3FB":{"unified":"1F3CB-1F3FB-200D-2640-FE0F","non_qualified":"1F3CB-1F3FB-200D-2640","image":"1f3cb-1f3fb-200d-2640-fe0f.png","sheet_x":10,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CB-1F3FC-200D-2640-FE0F","non_qualified":"1F3CB-1F3FC-200D-2640","image":"1f3cb-1f3fc-200d-2640-fe0f.png","sheet_x":10,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CB-1F3FD-200D-2640-FE0F","non_qualified":"1F3CB-1F3FD-200D-2640","image":"1f3cb-1f3fd-200d-2640-fe0f.png","sheet_x":10,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CB-1F3FE-200D-2640-FE0F","non_qualified":"1F3CB-1F3FE-200D-2640","image":"1f3cb-1f3fe-200d-2640-fe0f.png","sheet_x":10,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CB-1F3FF-200D-2640-FE0F","non_qualified":"1F3CB-1F3FF-200D-2640","image":"1f3cb-1f3ff-200d-2640-fe0f.png","sheet_x":10,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Lifting Weights","b":"1F3CB-FE0F-200D-2640-FE0F","k":[10,46],"o":7},"bicyclist":{"skin_variations":{"1F3FB":{"unified":"1F6B4-1F3FB","non_qualified":null,"image":"1f6b4-1f3fb.png","sheet_x":35,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F6B4-1F3FC","non_qualified":null,"image":"1f6b4-1f3fc.png","sheet_x":35,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F6B4-1F3FD","non_qualified":null,"image":"1f6b4-1f3fd.png","sheet_x":35,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F6B4-1F3FE","non_qualified":null,"image":"1f6b4-1f3fe.png","sheet_x":35,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F6B4-1F3FF","non_qualified":null,"image":"1f6b4-1f3ff.png","sheet_x":35,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F6B4-200D-2642-FE0F","a":"Bicyclist","b":"1F6B4","k":[35,37]},"man-biking":{"skin_variations":{"1F3FB":{"unified":"1F6B4-1F3FB-200D-2642-FE0F","non_qualified":"1F6B4-1F3FB-200D-2642","image":"1f6b4-1f3fb-200d-2642-fe0f.png","sheet_x":35,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6B4-1F3FC-200D-2642-FE0F","non_qualified":"1F6B4-1F3FC-200D-2642","image":"1f6b4-1f3fc-200d-2642-fe0f.png","sheet_x":35,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6B4-1F3FD-200D-2642-FE0F","non_qualified":"1F6B4-1F3FD-200D-2642","image":"1f6b4-1f3fd-200d-2642-fe0f.png","sheet_x":35,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6B4-1F3FE-200D-2642-FE0F","non_qualified":"1F6B4-1F3FE-200D-2642","image":"1f6b4-1f3fe-200d-2642-fe0f.png","sheet_x":35,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6B4-1F3FF-200D-2642-FE0F","non_qualified":"1F6B4-1F3FF-200D-2642","image":"1f6b4-1f3ff-200d-2642-fe0f.png","sheet_x":35,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F6B4","a":"Man Biking","b":"1F6B4-200D-2642-FE0F","c":"1F6B4-200D-2642","k":[35,31]},"woman-biking":{"skin_variations":{"1F3FB":{"unified":"1F6B4-1F3FB-200D-2640-FE0F","non_qualified":"1F6B4-1F3FB-200D-2640","image":"1f6b4-1f3fb-200d-2640-fe0f.png","sheet_x":35,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6B4-1F3FC-200D-2640-FE0F","non_qualified":"1F6B4-1F3FC-200D-2640","image":"1f6b4-1f3fc-200d-2640-fe0f.png","sheet_x":35,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6B4-1F3FD-200D-2640-FE0F","non_qualified":"1F6B4-1F3FD-200D-2640","image":"1f6b4-1f3fd-200d-2640-fe0f.png","sheet_x":35,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6B4-1F3FE-200D-2640-FE0F","non_qualified":"1F6B4-1F3FE-200D-2640","image":"1f6b4-1f3fe-200d-2640-fe0f.png","sheet_x":35,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6B4-1F3FF-200D-2640-FE0F","non_qualified":"1F6B4-1F3FF-200D-2640","image":"1f6b4-1f3ff-200d-2640-fe0f.png","sheet_x":35,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Biking","b":"1F6B4-200D-2640-FE0F","c":"1F6B4-200D-2640","k":[35,25]},"mountain_bicyclist":{"skin_variations":{"1F3FB":{"unified":"1F6B5-1F3FB","non_qualified":null,"image":"1f6b5-1f3fb.png","sheet_x":36,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F6B5-1F3FC","non_qualified":null,"image":"1f6b5-1f3fc.png","sheet_x":36,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F6B5-1F3FD","non_qualified":null,"image":"1f6b5-1f3fd.png","sheet_x":36,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F6B5-1F3FE","non_qualified":null,"image":"1f6b5-1f3fe.png","sheet_x":36,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F6B5-1F3FF","non_qualified":null,"image":"1f6b5-1f3ff.png","sheet_x":36,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F6B5-200D-2642-FE0F","a":"Mountain Bicyclist","b":"1F6B5","k":[36,3]},"man-mountain-biking":{"skin_variations":{"1F3FB":{"unified":"1F6B5-1F3FB-200D-2642-FE0F","non_qualified":"1F6B5-1F3FB-200D-2642","image":"1f6b5-1f3fb-200d-2642-fe0f.png","sheet_x":35,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6B5-1F3FC-200D-2642-FE0F","non_qualified":"1F6B5-1F3FC-200D-2642","image":"1f6b5-1f3fc-200d-2642-fe0f.png","sheet_x":35,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6B5-1F3FD-200D-2642-FE0F","non_qualified":"1F6B5-1F3FD-200D-2642","image":"1f6b5-1f3fd-200d-2642-fe0f.png","sheet_x":36,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6B5-1F3FE-200D-2642-FE0F","non_qualified":"1F6B5-1F3FE-200D-2642","image":"1f6b5-1f3fe-200d-2642-fe0f.png","sheet_x":36,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6B5-1F3FF-200D-2642-FE0F","non_qualified":"1F6B5-1F3FF-200D-2642","image":"1f6b5-1f3ff-200d-2642-fe0f.png","sheet_x":36,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F6B5","a":"Man Mountain Biking","b":"1F6B5-200D-2642-FE0F","c":"1F6B5-200D-2642","k":[35,49]},"woman-mountain-biking":{"skin_variations":{"1F3FB":{"unified":"1F6B5-1F3FB-200D-2640-FE0F","non_qualified":"1F6B5-1F3FB-200D-2640","image":"1f6b5-1f3fb-200d-2640-fe0f.png","sheet_x":35,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6B5-1F3FC-200D-2640-FE0F","non_qualified":"1F6B5-1F3FC-200D-2640","image":"1f6b5-1f3fc-200d-2640-fe0f.png","sheet_x":35,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6B5-1F3FD-200D-2640-FE0F","non_qualified":"1F6B5-1F3FD-200D-2640","image":"1f6b5-1f3fd-200d-2640-fe0f.png","sheet_x":35,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6B5-1F3FE-200D-2640-FE0F","non_qualified":"1F6B5-1F3FE-200D-2640","image":"1f6b5-1f3fe-200d-2640-fe0f.png","sheet_x":35,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6B5-1F3FF-200D-2640-FE0F","non_qualified":"1F6B5-1F3FF-200D-2640","image":"1f6b5-1f3ff-200d-2640-fe0f.png","sheet_x":35,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Mountain Biking","b":"1F6B5-200D-2640-FE0F","c":"1F6B5-200D-2640","k":[35,43]},"racing_car":{"a":"Racing Car","b":"1F3CE-FE0F","c":"1F3CE","j":["sports","race","fast","formula","f1"],"k":[11,31],"o":7},"racing_motorcycle":{"a":"Racing Motorcycle","b":"1F3CD-FE0F","c":"1F3CD","k":[11,30],"o":7},"person_doing_cartwheel":{"skin_variations":{"1F3FB":{"unified":"1F938-1F3FB","non_qualified":null,"image":"1f938-1f3fb.png","sheet_x":40,"sheet_y":25,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F938-1F3FC","non_qualified":null,"image":"1f938-1f3fc.png","sheet_x":40,"sheet_y":26,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F938-1F3FD","non_qualified":null,"image":"1f938-1f3fd.png","sheet_x":40,"sheet_y":27,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F938-1F3FE","non_qualified":null,"image":"1f938-1f3fe.png","sheet_x":40,"sheet_y":28,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F938-1F3FF","non_qualified":null,"image":"1f938-1f3ff.png","sheet_x":40,"sheet_y":29,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Person Doing Cartwheel","b":"1F938","k":[40,24],"o":9},"man-cartwheeling":{"skin_variations":{"1F3FB":{"unified":"1F938-1F3FB-200D-2642-FE0F","non_qualified":"1F938-1F3FB-200D-2642","image":"1f938-1f3fb-200d-2642-fe0f.png","sheet_x":40,"sheet_y":19,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F938-1F3FC-200D-2642-FE0F","non_qualified":"1F938-1F3FC-200D-2642","image":"1f938-1f3fc-200d-2642-fe0f.png","sheet_x":40,"sheet_y":20,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F938-1F3FD-200D-2642-FE0F","non_qualified":"1F938-1F3FD-200D-2642","image":"1f938-1f3fd-200d-2642-fe0f.png","sheet_x":40,"sheet_y":21,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F938-1F3FE-200D-2642-FE0F","non_qualified":"1F938-1F3FE-200D-2642","image":"1f938-1f3fe-200d-2642-fe0f.png","sheet_x":40,"sheet_y":22,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F938-1F3FF-200D-2642-FE0F","non_qualified":"1F938-1F3FF-200D-2642","image":"1f938-1f3ff-200d-2642-fe0f.png","sheet_x":40,"sheet_y":23,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Cartwheeling","b":"1F938-200D-2642-FE0F","c":"1F938-200D-2642","k":[40,18],"o":9},"woman-cartwheeling":{"skin_variations":{"1F3FB":{"unified":"1F938-1F3FB-200D-2640-FE0F","non_qualified":"1F938-1F3FB-200D-2640","image":"1f938-1f3fb-200d-2640-fe0f.png","sheet_x":40,"sheet_y":13,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F938-1F3FC-200D-2640-FE0F","non_qualified":"1F938-1F3FC-200D-2640","image":"1f938-1f3fc-200d-2640-fe0f.png","sheet_x":40,"sheet_y":14,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F938-1F3FD-200D-2640-FE0F","non_qualified":"1F938-1F3FD-200D-2640","image":"1f938-1f3fd-200d-2640-fe0f.png","sheet_x":40,"sheet_y":15,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F938-1F3FE-200D-2640-FE0F","non_qualified":"1F938-1F3FE-200D-2640","image":"1f938-1f3fe-200d-2640-fe0f.png","sheet_x":40,"sheet_y":16,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F938-1F3FF-200D-2640-FE0F","non_qualified":"1F938-1F3FF-200D-2640","image":"1f938-1f3ff-200d-2640-fe0f.png","sheet_x":40,"sheet_y":17,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Cartwheeling","b":"1F938-200D-2640-FE0F","c":"1F938-200D-2640","k":[40,12],"o":9},"wrestlers":{"a":"Wrestlers","b":"1F93C","k":[40,51],"o":9},"man-wrestling":{"a":"Man Wrestling","b":"1F93C-200D-2642-FE0F","c":"1F93C-200D-2642","k":[40,50],"o":9},"woman-wrestling":{"a":"Woman Wrestling","b":"1F93C-200D-2640-FE0F","c":"1F93C-200D-2640","k":[40,49],"o":9},"water_polo":{"skin_variations":{"1F3FB":{"unified":"1F93D-1F3FB","non_qualified":null,"image":"1f93d-1f3fb.png","sheet_x":41,"sheet_y":13,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F93D-1F3FC","non_qualified":null,"image":"1f93d-1f3fc.png","sheet_x":41,"sheet_y":14,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F93D-1F3FD","non_qualified":null,"image":"1f93d-1f3fd.png","sheet_x":41,"sheet_y":15,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F93D-1F3FE","non_qualified":null,"image":"1f93d-1f3fe.png","sheet_x":41,"sheet_y":16,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F93D-1F3FF","non_qualified":null,"image":"1f93d-1f3ff.png","sheet_x":41,"sheet_y":17,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Water Polo","b":"1F93D","k":[41,12],"o":9},"man-playing-water-polo":{"skin_variations":{"1F3FB":{"unified":"1F93D-1F3FB-200D-2642-FE0F","non_qualified":"1F93D-1F3FB-200D-2642","image":"1f93d-1f3fb-200d-2642-fe0f.png","sheet_x":41,"sheet_y":7,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F93D-1F3FC-200D-2642-FE0F","non_qualified":"1F93D-1F3FC-200D-2642","image":"1f93d-1f3fc-200d-2642-fe0f.png","sheet_x":41,"sheet_y":8,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F93D-1F3FD-200D-2642-FE0F","non_qualified":"1F93D-1F3FD-200D-2642","image":"1f93d-1f3fd-200d-2642-fe0f.png","sheet_x":41,"sheet_y":9,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F93D-1F3FE-200D-2642-FE0F","non_qualified":"1F93D-1F3FE-200D-2642","image":"1f93d-1f3fe-200d-2642-fe0f.png","sheet_x":41,"sheet_y":10,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F93D-1F3FF-200D-2642-FE0F","non_qualified":"1F93D-1F3FF-200D-2642","image":"1f93d-1f3ff-200d-2642-fe0f.png","sheet_x":41,"sheet_y":11,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Playing Water Polo","b":"1F93D-200D-2642-FE0F","c":"1F93D-200D-2642","k":[41,6],"o":9},"woman-playing-water-polo":{"skin_variations":{"1F3FB":{"unified":"1F93D-1F3FB-200D-2640-FE0F","non_qualified":"1F93D-1F3FB-200D-2640","image":"1f93d-1f3fb-200d-2640-fe0f.png","sheet_x":41,"sheet_y":1,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F93D-1F3FC-200D-2640-FE0F","non_qualified":"1F93D-1F3FC-200D-2640","image":"1f93d-1f3fc-200d-2640-fe0f.png","sheet_x":41,"sheet_y":2,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F93D-1F3FD-200D-2640-FE0F","non_qualified":"1F93D-1F3FD-200D-2640","image":"1f93d-1f3fd-200d-2640-fe0f.png","sheet_x":41,"sheet_y":3,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F93D-1F3FE-200D-2640-FE0F","non_qualified":"1F93D-1F3FE-200D-2640","image":"1f93d-1f3fe-200d-2640-fe0f.png","sheet_x":41,"sheet_y":4,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F93D-1F3FF-200D-2640-FE0F","non_qualified":"1F93D-1F3FF-200D-2640","image":"1f93d-1f3ff-200d-2640-fe0f.png","sheet_x":41,"sheet_y":5,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Playing Water Polo","b":"1F93D-200D-2640-FE0F","c":"1F93D-200D-2640","k":[41,0],"o":9},"handball":{"skin_variations":{"1F3FB":{"unified":"1F93E-1F3FB","non_qualified":null,"image":"1f93e-1f3fb.png","sheet_x":41,"sheet_y":31,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F93E-1F3FC","non_qualified":null,"image":"1f93e-1f3fc.png","sheet_x":41,"sheet_y":32,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F93E-1F3FD","non_qualified":null,"image":"1f93e-1f3fd.png","sheet_x":41,"sheet_y":33,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F93E-1F3FE","non_qualified":null,"image":"1f93e-1f3fe.png","sheet_x":41,"sheet_y":34,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F93E-1F3FF","non_qualified":null,"image":"1f93e-1f3ff.png","sheet_x":41,"sheet_y":35,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Handball","b":"1F93E","k":[41,30],"o":9},"man-playing-handball":{"skin_variations":{"1F3FB":{"unified":"1F93E-1F3FB-200D-2642-FE0F","non_qualified":"1F93E-1F3FB-200D-2642","image":"1f93e-1f3fb-200d-2642-fe0f.png","sheet_x":41,"sheet_y":25,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F93E-1F3FC-200D-2642-FE0F","non_qualified":"1F93E-1F3FC-200D-2642","image":"1f93e-1f3fc-200d-2642-fe0f.png","sheet_x":41,"sheet_y":26,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F93E-1F3FD-200D-2642-FE0F","non_qualified":"1F93E-1F3FD-200D-2642","image":"1f93e-1f3fd-200d-2642-fe0f.png","sheet_x":41,"sheet_y":27,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F93E-1F3FE-200D-2642-FE0F","non_qualified":"1F93E-1F3FE-200D-2642","image":"1f93e-1f3fe-200d-2642-fe0f.png","sheet_x":41,"sheet_y":28,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F93E-1F3FF-200D-2642-FE0F","non_qualified":"1F93E-1F3FF-200D-2642","image":"1f93e-1f3ff-200d-2642-fe0f.png","sheet_x":41,"sheet_y":29,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Playing Handball","b":"1F93E-200D-2642-FE0F","c":"1F93E-200D-2642","k":[41,24],"o":9},"woman-playing-handball":{"skin_variations":{"1F3FB":{"unified":"1F93E-1F3FB-200D-2640-FE0F","non_qualified":"1F93E-1F3FB-200D-2640","image":"1f93e-1f3fb-200d-2640-fe0f.png","sheet_x":41,"sheet_y":19,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F93E-1F3FC-200D-2640-FE0F","non_qualified":"1F93E-1F3FC-200D-2640","image":"1f93e-1f3fc-200d-2640-fe0f.png","sheet_x":41,"sheet_y":20,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F93E-1F3FD-200D-2640-FE0F","non_qualified":"1F93E-1F3FD-200D-2640","image":"1f93e-1f3fd-200d-2640-fe0f.png","sheet_x":41,"sheet_y":21,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F93E-1F3FE-200D-2640-FE0F","non_qualified":"1F93E-1F3FE-200D-2640","image":"1f93e-1f3fe-200d-2640-fe0f.png","sheet_x":41,"sheet_y":22,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F93E-1F3FF-200D-2640-FE0F","non_qualified":"1F93E-1F3FF-200D-2640","image":"1f93e-1f3ff-200d-2640-fe0f.png","sheet_x":41,"sheet_y":23,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Playing Handball","b":"1F93E-200D-2640-FE0F","c":"1F93E-200D-2640","k":[41,18],"o":9},"juggling":{"skin_variations":{"1F3FB":{"unified":"1F939-1F3FB","non_qualified":null,"image":"1f939-1f3fb.png","sheet_x":40,"sheet_y":43,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F939-1F3FC","non_qualified":null,"image":"1f939-1f3fc.png","sheet_x":40,"sheet_y":44,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F939-1F3FD","non_qualified":null,"image":"1f939-1f3fd.png","sheet_x":40,"sheet_y":45,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F939-1F3FE","non_qualified":null,"image":"1f939-1f3fe.png","sheet_x":40,"sheet_y":46,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F939-1F3FF","non_qualified":null,"image":"1f939-1f3ff.png","sheet_x":40,"sheet_y":47,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Juggling","b":"1F939","k":[40,42],"o":9},"man-juggling":{"skin_variations":{"1F3FB":{"unified":"1F939-1F3FB-200D-2642-FE0F","non_qualified":"1F939-1F3FB-200D-2642","image":"1f939-1f3fb-200d-2642-fe0f.png","sheet_x":40,"sheet_y":37,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F939-1F3FC-200D-2642-FE0F","non_qualified":"1F939-1F3FC-200D-2642","image":"1f939-1f3fc-200d-2642-fe0f.png","sheet_x":40,"sheet_y":38,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F939-1F3FD-200D-2642-FE0F","non_qualified":"1F939-1F3FD-200D-2642","image":"1f939-1f3fd-200d-2642-fe0f.png","sheet_x":40,"sheet_y":39,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F939-1F3FE-200D-2642-FE0F","non_qualified":"1F939-1F3FE-200D-2642","image":"1f939-1f3fe-200d-2642-fe0f.png","sheet_x":40,"sheet_y":40,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F939-1F3FF-200D-2642-FE0F","non_qualified":"1F939-1F3FF-200D-2642","image":"1f939-1f3ff-200d-2642-fe0f.png","sheet_x":40,"sheet_y":41,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Juggling","b":"1F939-200D-2642-FE0F","c":"1F939-200D-2642","k":[40,36],"o":9},"woman-juggling":{"skin_variations":{"1F3FB":{"unified":"1F939-1F3FB-200D-2640-FE0F","non_qualified":"1F939-1F3FB-200D-2640","image":"1f939-1f3fb-200d-2640-fe0f.png","sheet_x":40,"sheet_y":31,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F939-1F3FC-200D-2640-FE0F","non_qualified":"1F939-1F3FC-200D-2640","image":"1f939-1f3fc-200d-2640-fe0f.png","sheet_x":40,"sheet_y":32,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F939-1F3FD-200D-2640-FE0F","non_qualified":"1F939-1F3FD-200D-2640","image":"1f939-1f3fd-200d-2640-fe0f.png","sheet_x":40,"sheet_y":33,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F939-1F3FE-200D-2640-FE0F","non_qualified":"1F939-1F3FE-200D-2640","image":"1f939-1f3fe-200d-2640-fe0f.png","sheet_x":40,"sheet_y":34,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F939-1F3FF-200D-2640-FE0F","non_qualified":"1F939-1F3FF-200D-2640","image":"1f939-1f3ff-200d-2640-fe0f.png","sheet_x":40,"sheet_y":35,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Juggling","b":"1F939-200D-2640-FE0F","c":"1F939-200D-2640","k":[40,30],"o":9},"couple":{"a":"Man and Woman Holding Hands","b":"1F46B","j":["pair","people","human","love","date","dating","like","affection","valentines","marriage"],"k":[20,30],"n":["man_and_woman_holding_hands"]},"two_men_holding_hands":{"a":"Two Men Holding Hands","b":"1F46C","j":["pair","couple","love","like","bromance","friendship","people","human"],"k":[20,31]},"two_women_holding_hands":{"a":"Two Women Holding Hands","b":"1F46D","j":["pair","friendship","couple","love","like","female","people","human"],"k":[20,32]},"couplekiss":{"obsoleted_by":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F468","a":"Kiss","b":"1F48F","k":[24,41]},"woman-kiss-man":{"obsoletes":"1F48F","a":"Woman Kiss Man","b":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F468","c":"1F469-200D-2764-200D-1F48B-200D-1F468","k":[20,21]},"man-kiss-man":{"a":"Man Kiss Man","b":"1F468-200D-2764-FE0F-200D-1F48B-200D-1F468","c":"1F468-200D-2764-200D-1F48B-200D-1F468","k":[18,10]},"woman-kiss-woman":{"a":"Woman Kiss Woman","b":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F469","c":"1F469-200D-2764-200D-1F48B-200D-1F469","k":[20,22]},"couple_with_heart":{"obsoleted_by":"1F469-200D-2764-FE0F-200D-1F468","a":"Couple with Heart","b":"1F491","k":[24,43]},"woman-heart-man":{"obsoletes":"1F491","a":"Woman Heart Man","b":"1F469-200D-2764-FE0F-200D-1F468","c":"1F469-200D-2764-200D-1F468","k":[20,19]},"man-heart-man":{"a":"Man Heart Man","b":"1F468-200D-2764-FE0F-200D-1F468","c":"1F468-200D-2764-200D-1F468","k":[18,9]},"woman-heart-woman":{"a":"Woman Heart Woman","b":"1F469-200D-2764-FE0F-200D-1F469","c":"1F469-200D-2764-200D-1F469","k":[20,20]},"family":{"obsoleted_by":"1F468-200D-1F469-200D-1F466","a":"Family","b":"1F46A","k":[20,29],"n":["man-woman-boy"]},"man-woman-boy":{"obsoletes":"1F46A","a":"Man Woman Boy","b":"1F468-200D-1F469-200D-1F466","k":[17,2],"n":["family"]},"man-woman-girl":{"a":"Man Woman Girl","b":"1F468-200D-1F469-200D-1F467","k":[17,4]},"man-woman-girl-boy":{"a":"Man Woman Girl Boy","b":"1F468-200D-1F469-200D-1F467-200D-1F466","k":[17,5]},"man-woman-boy-boy":{"a":"Man Woman Boy Boy","b":"1F468-200D-1F469-200D-1F466-200D-1F466","k":[17,3]},"man-woman-girl-girl":{"a":"Man Woman Girl Girl","b":"1F468-200D-1F469-200D-1F467-200D-1F467","k":[17,6]},"man-man-boy":{"a":"Man Man Boy","b":"1F468-200D-1F468-200D-1F466","k":[16,49]},"man-man-girl":{"a":"Man Man Girl","b":"1F468-200D-1F468-200D-1F467","k":[16,51]},"man-man-girl-boy":{"a":"Man Man Girl Boy","b":"1F468-200D-1F468-200D-1F467-200D-1F466","k":[17,0]},"man-man-boy-boy":{"a":"Man Man Boy Boy","b":"1F468-200D-1F468-200D-1F466-200D-1F466","k":[16,50]},"man-man-girl-girl":{"a":"Man Man Girl Girl","b":"1F468-200D-1F468-200D-1F467-200D-1F467","k":[17,1]},"woman-woman-boy":{"a":"Woman Woman Boy","b":"1F469-200D-1F469-200D-1F466","k":[19,12]},"woman-woman-girl":{"a":"Woman Woman Girl","b":"1F469-200D-1F469-200D-1F467","k":[19,14]},"woman-woman-girl-boy":{"a":"Woman Woman Girl Boy","b":"1F469-200D-1F469-200D-1F467-200D-1F466","k":[19,15]},"woman-woman-boy-boy":{"a":"Woman Woman Boy Boy","b":"1F469-200D-1F469-200D-1F466-200D-1F466","k":[19,13]},"woman-woman-girl-girl":{"a":"Woman Woman Girl Girl","b":"1F469-200D-1F469-200D-1F467-200D-1F467","k":[19,16]},"man-boy":{"a":"Man Boy","b":"1F468-200D-1F466","k":[16,45]},"man-boy-boy":{"a":"Man Boy Boy","b":"1F468-200D-1F466-200D-1F466","k":[16,44]},"man-girl":{"a":"Man Girl","b":"1F468-200D-1F467","k":[16,48]},"man-girl-boy":{"a":"Man Girl Boy","b":"1F468-200D-1F467-200D-1F466","k":[16,46]},"man-girl-girl":{"a":"Man Girl Girl","b":"1F468-200D-1F467-200D-1F467","k":[16,47]},"woman-boy":{"a":"Woman Boy","b":"1F469-200D-1F466","k":[19,8]},"woman-boy-boy":{"a":"Woman Boy Boy","b":"1F469-200D-1F466-200D-1F466","k":[19,7]},"woman-girl":{"a":"Woman Girl","b":"1F469-200D-1F467","k":[19,11]},"woman-girl-boy":{"a":"Woman Girl Boy","b":"1F469-200D-1F467-200D-1F466","k":[19,9]},"woman-girl-girl":{"a":"Woman Girl Girl","b":"1F469-200D-1F467-200D-1F467","k":[19,10]},"selfie":{"skin_variations":{"1F3FB":{"unified":"1F933-1F3FB","non_qualified":null,"image":"1f933-1f3fb.png","sheet_x":39,"sheet_y":23,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F933-1F3FC","non_qualified":null,"image":"1f933-1f3fc.png","sheet_x":39,"sheet_y":24,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F933-1F3FD","non_qualified":null,"image":"1f933-1f3fd.png","sheet_x":39,"sheet_y":25,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F933-1F3FE","non_qualified":null,"image":"1f933-1f3fe.png","sheet_x":39,"sheet_y":26,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F933-1F3FF","non_qualified":null,"image":"1f933-1f3ff.png","sheet_x":39,"sheet_y":27,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Selfie","b":"1F933","j":["camera","phone"],"k":[39,22],"o":9},"muscle":{"skin_variations":{"1F3FB":{"unified":"1F4AA-1F3FB","non_qualified":null,"image":"1f4aa-1f3fb.png","sheet_x":25,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F4AA-1F3FC","non_qualified":null,"image":"1f4aa-1f3fc.png","sheet_x":25,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F4AA-1F3FD","non_qualified":null,"image":"1f4aa-1f3fd.png","sheet_x":25,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F4AA-1F3FE","non_qualified":null,"image":"1f4aa-1f3fe.png","sheet_x":25,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F4AA-1F3FF","non_qualified":null,"image":"1f4aa-1f3ff.png","sheet_x":25,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Flexed Biceps","b":"1F4AA","j":["arm","flex","hand","summer","strong","biceps"],"k":[25,16]},"point_left":{"skin_variations":{"1F3FB":{"unified":"1F448-1F3FB","non_qualified":null,"image":"1f448-1f3fb.png","sheet_x":14,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F448-1F3FC","non_qualified":null,"image":"1f448-1f3fc.png","sheet_x":14,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F448-1F3FD","non_qualified":null,"image":"1f448-1f3fd.png","sheet_x":14,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F448-1F3FE","non_qualified":null,"image":"1f448-1f3fe.png","sheet_x":14,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F448-1F3FF","non_qualified":null,"image":"1f448-1f3ff.png","sheet_x":14,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"White Left Pointing Backhand Index","b":"1F448","j":["direction","fingers","hand","left"],"k":[14,19]},"point_right":{"skin_variations":{"1F3FB":{"unified":"1F449-1F3FB","non_qualified":null,"image":"1f449-1f3fb.png","sheet_x":14,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F449-1F3FC","non_qualified":null,"image":"1f449-1f3fc.png","sheet_x":14,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F449-1F3FD","non_qualified":null,"image":"1f449-1f3fd.png","sheet_x":14,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F449-1F3FE","non_qualified":null,"image":"1f449-1f3fe.png","sheet_x":14,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F449-1F3FF","non_qualified":null,"image":"1f449-1f3ff.png","sheet_x":14,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"White Right Pointing Backhand Index","b":"1F449","j":["fingers","hand","direction","right"],"k":[14,25]},"point_up":{"skin_variations":{"1F3FB":{"unified":"261D-1F3FB","non_qualified":null,"image":"261d-1f3fb.png","sheet_x":47,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"261D-1F3FC","non_qualified":null,"image":"261d-1f3fc.png","sheet_x":47,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"261D-1F3FD","non_qualified":null,"image":"261d-1f3fd.png","sheet_x":47,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"261D-1F3FE","non_qualified":null,"image":"261d-1f3fe.png","sheet_x":47,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"261D-1F3FF","non_qualified":null,"image":"261d-1f3ff.png","sheet_x":47,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"White Up Pointing Index","b":"261D-FE0F","c":"261D","j":["hand","fingers","direction","up"],"k":[47,26],"o":1},"point_up_2":{"skin_variations":{"1F3FB":{"unified":"1F446-1F3FB","non_qualified":null,"image":"1f446-1f3fb.png","sheet_x":14,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F446-1F3FC","non_qualified":null,"image":"1f446-1f3fc.png","sheet_x":14,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F446-1F3FD","non_qualified":null,"image":"1f446-1f3fd.png","sheet_x":14,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F446-1F3FE","non_qualified":null,"image":"1f446-1f3fe.png","sheet_x":14,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F446-1F3FF","non_qualified":null,"image":"1f446-1f3ff.png","sheet_x":14,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"White Up Pointing Backhand Index","b":"1F446","j":["fingers","hand","direction","up"],"k":[14,7]},"middle_finger":{"skin_variations":{"1F3FB":{"unified":"1F595-1F3FB","non_qualified":null,"image":"1f595-1f3fb.png","sheet_x":29,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F595-1F3FC","non_qualified":null,"image":"1f595-1f3fc.png","sheet_x":29,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F595-1F3FD","non_qualified":null,"image":"1f595-1f3fd.png","sheet_x":29,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F595-1F3FE","non_qualified":null,"image":"1f595-1f3fe.png","sheet_x":29,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F595-1F3FF","non_qualified":null,"image":"1f595-1f3ff.png","sheet_x":29,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Reversed Hand with Middle Finger Extended","b":"1F595","k":[29,38],"n":["reversed_hand_with_middle_finger_extended"],"o":7},"point_down":{"skin_variations":{"1F3FB":{"unified":"1F447-1F3FB","non_qualified":null,"image":"1f447-1f3fb.png","sheet_x":14,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F447-1F3FC","non_qualified":null,"image":"1f447-1f3fc.png","sheet_x":14,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F447-1F3FD","non_qualified":null,"image":"1f447-1f3fd.png","sheet_x":14,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F447-1F3FE","non_qualified":null,"image":"1f447-1f3fe.png","sheet_x":14,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F447-1F3FF","non_qualified":null,"image":"1f447-1f3ff.png","sheet_x":14,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"White Down Pointing Backhand Index","b":"1F447","j":["fingers","hand","direction","down"],"k":[14,13]},"v":{"skin_variations":{"1F3FB":{"unified":"270C-1F3FB","non_qualified":null,"image":"270c-1f3fb.png","sheet_x":49,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"270C-1F3FC","non_qualified":null,"image":"270c-1f3fc.png","sheet_x":49,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"270C-1F3FD","non_qualified":null,"image":"270c-1f3fd.png","sheet_x":49,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"270C-1F3FE","non_qualified":null,"image":"270c-1f3fe.png","sheet_x":49,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"270C-1F3FF","non_qualified":null,"image":"270c-1f3ff.png","sheet_x":49,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Victory Hand","b":"270C-FE0F","c":"270C","j":["fingers","ohyeah","hand","peace","victory","two"],"k":[49,30],"o":1},"crossed_fingers":{"skin_variations":{"1F3FB":{"unified":"1F91E-1F3FB","non_qualified":null,"image":"1f91e-1f3fb.png","sheet_x":38,"sheet_y":12,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F91E-1F3FC","non_qualified":null,"image":"1f91e-1f3fc.png","sheet_x":38,"sheet_y":13,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F91E-1F3FD","non_qualified":null,"image":"1f91e-1f3fd.png","sheet_x":38,"sheet_y":14,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F91E-1F3FE","non_qualified":null,"image":"1f91e-1f3fe.png","sheet_x":38,"sheet_y":15,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F91E-1F3FF","non_qualified":null,"image":"1f91e-1f3ff.png","sheet_x":38,"sheet_y":16,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Hand with Index and Middle Fingers Crossed","b":"1F91E","j":["good","lucky"],"k":[38,11],"n":["hand_with_index_and_middle_fingers_crossed"],"o":9},"spock-hand":{"skin_variations":{"1F3FB":{"unified":"1F596-1F3FB","non_qualified":null,"image":"1f596-1f3fb.png","sheet_x":29,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F596-1F3FC","non_qualified":null,"image":"1f596-1f3fc.png","sheet_x":29,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F596-1F3FD","non_qualified":null,"image":"1f596-1f3fd.png","sheet_x":29,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F596-1F3FE","non_qualified":null,"image":"1f596-1f3fe.png","sheet_x":29,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F596-1F3FF","non_qualified":null,"image":"1f596-1f3ff.png","sheet_x":29,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Raised Hand with Part Between Middle and Ring Fingers","b":"1F596","k":[29,44],"o":7},"the_horns":{"skin_variations":{"1F3FB":{"unified":"1F918-1F3FB","non_qualified":null,"image":"1f918-1f3fb.png","sheet_x":37,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F918-1F3FC","non_qualified":null,"image":"1f918-1f3fc.png","sheet_x":37,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F918-1F3FD","non_qualified":null,"image":"1f918-1f3fd.png","sheet_x":37,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F918-1F3FE","non_qualified":null,"image":"1f918-1f3fe.png","sheet_x":37,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F918-1F3FF","non_qualified":null,"image":"1f918-1f3ff.png","sheet_x":37,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Sign of the Horns","b":"1F918","k":[37,32],"n":["sign_of_the_horns"],"o":8},"call_me_hand":{"skin_variations":{"1F3FB":{"unified":"1F919-1F3FB","non_qualified":null,"image":"1f919-1f3fb.png","sheet_x":37,"sheet_y":39,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F919-1F3FC","non_qualified":null,"image":"1f919-1f3fc.png","sheet_x":37,"sheet_y":40,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F919-1F3FD","non_qualified":null,"image":"1f919-1f3fd.png","sheet_x":37,"sheet_y":41,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F919-1F3FE","non_qualified":null,"image":"1f919-1f3fe.png","sheet_x":37,"sheet_y":42,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F919-1F3FF","non_qualified":null,"image":"1f919-1f3ff.png","sheet_x":37,"sheet_y":43,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Call Me Hand","b":"1F919","j":["hands","gesture"],"k":[37,38],"o":9},"raised_hand_with_fingers_splayed":{"skin_variations":{"1F3FB":{"unified":"1F590-1F3FB","non_qualified":null,"image":"1f590-1f3fb.png","sheet_x":29,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F590-1F3FC","non_qualified":null,"image":"1f590-1f3fc.png","sheet_x":29,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F590-1F3FD","non_qualified":null,"image":"1f590-1f3fd.png","sheet_x":29,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F590-1F3FE","non_qualified":null,"image":"1f590-1f3fe.png","sheet_x":29,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F590-1F3FF","non_qualified":null,"image":"1f590-1f3ff.png","sheet_x":29,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Raised Hand with Fingers Splayed","b":"1F590-FE0F","c":"1F590","j":["hand","fingers","palm"],"k":[29,32],"o":7},"hand":{"skin_variations":{"1F3FB":{"unified":"270B-1F3FB","non_qualified":null,"image":"270b-1f3fb.png","sheet_x":49,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"270B-1F3FC","non_qualified":null,"image":"270b-1f3fc.png","sheet_x":49,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"270B-1F3FD","non_qualified":null,"image":"270b-1f3fd.png","sheet_x":49,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"270B-1F3FE","non_qualified":null,"image":"270b-1f3fe.png","sheet_x":49,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"270B-1F3FF","non_qualified":null,"image":"270b-1f3ff.png","sheet_x":49,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Raised Hand","b":"270B","k":[49,24],"n":["raised_hand"]},"ok_hand":{"skin_variations":{"1F3FB":{"unified":"1F44C-1F3FB","non_qualified":null,"image":"1f44c-1f3fb.png","sheet_x":14,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F44C-1F3FC","non_qualified":null,"image":"1f44c-1f3fc.png","sheet_x":14,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F44C-1F3FD","non_qualified":null,"image":"1f44c-1f3fd.png","sheet_x":14,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F44C-1F3FE","non_qualified":null,"image":"1f44c-1f3fe.png","sheet_x":14,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F44C-1F3FF","non_qualified":null,"image":"1f44c-1f3ff.png","sheet_x":14,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Ok Hand Sign","b":"1F44C","j":["fingers","limbs","perfect","ok","okay"],"k":[14,43]},"+1":{"skin_variations":{"1F3FB":{"unified":"1F44D-1F3FB","non_qualified":null,"image":"1f44d-1f3fb.png","sheet_x":14,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F44D-1F3FC","non_qualified":null,"image":"1f44d-1f3fc.png","sheet_x":14,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F44D-1F3FD","non_qualified":null,"image":"1f44d-1f3fd.png","sheet_x":15,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F44D-1F3FE","non_qualified":null,"image":"1f44d-1f3fe.png","sheet_x":15,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F44D-1F3FF","non_qualified":null,"image":"1f44d-1f3ff.png","sheet_x":15,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Thumbs Up Sign","b":"1F44D","j":["thumbsup","yes","awesome","good","agree","accept","cool","hand","like"],"k":[14,49],"n":["thumbsup"]},"-1":{"skin_variations":{"1F3FB":{"unified":"1F44E-1F3FB","non_qualified":null,"image":"1f44e-1f3fb.png","sheet_x":15,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F44E-1F3FC","non_qualified":null,"image":"1f44e-1f3fc.png","sheet_x":15,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F44E-1F3FD","non_qualified":null,"image":"1f44e-1f3fd.png","sheet_x":15,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F44E-1F3FE","non_qualified":null,"image":"1f44e-1f3fe.png","sheet_x":15,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F44E-1F3FF","non_qualified":null,"image":"1f44e-1f3ff.png","sheet_x":15,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Thumbs Down Sign","b":"1F44E","j":["thumbsdown","no","dislike","hand"],"k":[15,3],"n":["thumbsdown"]},"fist":{"skin_variations":{"1F3FB":{"unified":"270A-1F3FB","non_qualified":null,"image":"270a-1f3fb.png","sheet_x":49,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"270A-1F3FC","non_qualified":null,"image":"270a-1f3fc.png","sheet_x":49,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"270A-1F3FD","non_qualified":null,"image":"270a-1f3fd.png","sheet_x":49,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"270A-1F3FE","non_qualified":null,"image":"270a-1f3fe.png","sheet_x":49,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"270A-1F3FF","non_qualified":null,"image":"270a-1f3ff.png","sheet_x":49,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Raised Fist","b":"270A","j":["fingers","hand","grasp"],"k":[49,18]},"facepunch":{"skin_variations":{"1F3FB":{"unified":"1F44A-1F3FB","non_qualified":null,"image":"1f44a-1f3fb.png","sheet_x":14,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F44A-1F3FC","non_qualified":null,"image":"1f44a-1f3fc.png","sheet_x":14,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F44A-1F3FD","non_qualified":null,"image":"1f44a-1f3fd.png","sheet_x":14,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F44A-1F3FE","non_qualified":null,"image":"1f44a-1f3fe.png","sheet_x":14,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F44A-1F3FF","non_qualified":null,"image":"1f44a-1f3ff.png","sheet_x":14,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Fisted Hand Sign","b":"1F44A","j":["angry","violence","fist","hit","attack","hand"],"k":[14,31],"n":["punch"]},"left-facing_fist":{"skin_variations":{"1F3FB":{"unified":"1F91B-1F3FB","non_qualified":null,"image":"1f91b-1f3fb.png","sheet_x":37,"sheet_y":51,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F91B-1F3FC","non_qualified":null,"image":"1f91b-1f3fc.png","sheet_x":38,"sheet_y":0,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F91B-1F3FD","non_qualified":null,"image":"1f91b-1f3fd.png","sheet_x":38,"sheet_y":1,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F91B-1F3FE","non_qualified":null,"image":"1f91b-1f3fe.png","sheet_x":38,"sheet_y":2,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F91B-1F3FF","non_qualified":null,"image":"1f91b-1f3ff.png","sheet_x":38,"sheet_y":3,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Left-Facing Fist","b":"1F91B","k":[37,50],"o":9},"right-facing_fist":{"skin_variations":{"1F3FB":{"unified":"1F91C-1F3FB","non_qualified":null,"image":"1f91c-1f3fb.png","sheet_x":38,"sheet_y":5,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F91C-1F3FC","non_qualified":null,"image":"1f91c-1f3fc.png","sheet_x":38,"sheet_y":6,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F91C-1F3FD","non_qualified":null,"image":"1f91c-1f3fd.png","sheet_x":38,"sheet_y":7,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F91C-1F3FE","non_qualified":null,"image":"1f91c-1f3fe.png","sheet_x":38,"sheet_y":8,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F91C-1F3FF","non_qualified":null,"image":"1f91c-1f3ff.png","sheet_x":38,"sheet_y":9,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Right-Facing Fist","b":"1F91C","k":[38,4],"o":9},"raised_back_of_hand":{"skin_variations":{"1F3FB":{"unified":"1F91A-1F3FB","non_qualified":null,"image":"1f91a-1f3fb.png","sheet_x":37,"sheet_y":45,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F91A-1F3FC","non_qualified":null,"image":"1f91a-1f3fc.png","sheet_x":37,"sheet_y":46,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F91A-1F3FD","non_qualified":null,"image":"1f91a-1f3fd.png","sheet_x":37,"sheet_y":47,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F91A-1F3FE","non_qualified":null,"image":"1f91a-1f3fe.png","sheet_x":37,"sheet_y":48,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F91A-1F3FF","non_qualified":null,"image":"1f91a-1f3ff.png","sheet_x":37,"sheet_y":49,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Raised Back of Hand","b":"1F91A","j":["fingers","raised","backhand"],"k":[37,44],"o":9},"wave":{"skin_variations":{"1F3FB":{"unified":"1F44B-1F3FB","non_qualified":null,"image":"1f44b-1f3fb.png","sheet_x":14,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F44B-1F3FC","non_qualified":null,"image":"1f44b-1f3fc.png","sheet_x":14,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F44B-1F3FD","non_qualified":null,"image":"1f44b-1f3fd.png","sheet_x":14,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F44B-1F3FE","non_qualified":null,"image":"1f44b-1f3fe.png","sheet_x":14,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F44B-1F3FF","non_qualified":null,"image":"1f44b-1f3ff.png","sheet_x":14,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Waving Hand Sign","b":"1F44B","j":["hands","gesture","goodbye","solong","farewell","hello","hi","palm"],"k":[14,37]},"i_love_you_hand_sign":{"skin_variations":{"1F3FB":{"unified":"1F91F-1F3FB","non_qualified":null,"image":"1f91f-1f3fb.png","sheet_x":38,"sheet_y":18,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F91F-1F3FC","non_qualified":null,"image":"1f91f-1f3fc.png","sheet_x":38,"sheet_y":19,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F91F-1F3FD","non_qualified":null,"image":"1f91f-1f3fd.png","sheet_x":38,"sheet_y":20,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F91F-1F3FE","non_qualified":null,"image":"1f91f-1f3fe.png","sheet_x":38,"sheet_y":21,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F91F-1F3FF","non_qualified":null,"image":"1f91f-1f3ff.png","sheet_x":38,"sheet_y":22,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"I Love You Hand Sign","b":"1F91F","k":[38,17],"o":10},"writing_hand":{"skin_variations":{"1F3FB":{"unified":"270D-1F3FB","non_qualified":null,"image":"270d-1f3fb.png","sheet_x":49,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"270D-1F3FC","non_qualified":null,"image":"270d-1f3fc.png","sheet_x":49,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"270D-1F3FD","non_qualified":null,"image":"270d-1f3fd.png","sheet_x":49,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"270D-1F3FE","non_qualified":null,"image":"270d-1f3fe.png","sheet_x":49,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"270D-1F3FF","non_qualified":null,"image":"270d-1f3ff.png","sheet_x":49,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Writing Hand","b":"270D-FE0F","c":"270D","j":["lower_left_ballpoint_pen","stationery","write","compose"],"k":[49,36],"o":1},"clap":{"skin_variations":{"1F3FB":{"unified":"1F44F-1F3FB","non_qualified":null,"image":"1f44f-1f3fb.png","sheet_x":15,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F44F-1F3FC","non_qualified":null,"image":"1f44f-1f3fc.png","sheet_x":15,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F44F-1F3FD","non_qualified":null,"image":"1f44f-1f3fd.png","sheet_x":15,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F44F-1F3FE","non_qualified":null,"image":"1f44f-1f3fe.png","sheet_x":15,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F44F-1F3FF","non_qualified":null,"image":"1f44f-1f3ff.png","sheet_x":15,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Clapping Hands Sign","b":"1F44F","j":["hands","praise","applause","congrats","yay"],"k":[15,9]},"open_hands":{"skin_variations":{"1F3FB":{"unified":"1F450-1F3FB","non_qualified":null,"image":"1f450-1f3fb.png","sheet_x":15,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F450-1F3FC","non_qualified":null,"image":"1f450-1f3fc.png","sheet_x":15,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F450-1F3FD","non_qualified":null,"image":"1f450-1f3fd.png","sheet_x":15,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F450-1F3FE","non_qualified":null,"image":"1f450-1f3fe.png","sheet_x":15,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F450-1F3FF","non_qualified":null,"image":"1f450-1f3ff.png","sheet_x":15,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Open Hands Sign","b":"1F450","j":["fingers","butterfly","hands","open"],"k":[15,15]},"raised_hands":{"skin_variations":{"1F3FB":{"unified":"1F64C-1F3FB","non_qualified":null,"image":"1f64c-1f3fb.png","sheet_x":33,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F64C-1F3FC","non_qualified":null,"image":"1f64c-1f3fc.png","sheet_x":33,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F64C-1F3FD","non_qualified":null,"image":"1f64c-1f3fd.png","sheet_x":33,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F64C-1F3FE","non_qualified":null,"image":"1f64c-1f3fe.png","sheet_x":33,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F64C-1F3FF","non_qualified":null,"image":"1f64c-1f3ff.png","sheet_x":33,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Person Raising Both Hands in Celebration","b":"1F64C","j":["gesture","hooray","yea","celebration","hands"],"k":[33,12]},"palms_up_together":{"skin_variations":{"1F3FB":{"unified":"1F932-1F3FB","non_qualified":null,"image":"1f932-1f3fb.png","sheet_x":39,"sheet_y":17,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F932-1F3FC","non_qualified":null,"image":"1f932-1f3fc.png","sheet_x":39,"sheet_y":18,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F932-1F3FD","non_qualified":null,"image":"1f932-1f3fd.png","sheet_x":39,"sheet_y":19,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F932-1F3FE","non_qualified":null,"image":"1f932-1f3fe.png","sheet_x":39,"sheet_y":20,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F932-1F3FF","non_qualified":null,"image":"1f932-1f3ff.png","sheet_x":39,"sheet_y":21,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Palms Up Together","b":"1F932","k":[39,16],"o":10},"pray":{"skin_variations":{"1F3FB":{"unified":"1F64F-1F3FB","non_qualified":null,"image":"1f64f-1f3fb.png","sheet_x":34,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F64F-1F3FC","non_qualified":null,"image":"1f64f-1f3fc.png","sheet_x":34,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F64F-1F3FD","non_qualified":null,"image":"1f64f-1f3fd.png","sheet_x":34,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F64F-1F3FE","non_qualified":null,"image":"1f64f-1f3fe.png","sheet_x":34,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F64F-1F3FF","non_qualified":null,"image":"1f64f-1f3ff.png","sheet_x":34,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Person with Folded Hands","b":"1F64F","j":["please","hope","wish","namaste","highfive"],"k":[34,2]},"handshake":{"a":"Handshake","b":"1F91D","j":["agreement","shake"],"k":[38,10],"o":9},"nail_care":{"skin_variations":{"1F3FB":{"unified":"1F485-1F3FB","non_qualified":null,"image":"1f485-1f3fb.png","sheet_x":23,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F485-1F3FC","non_qualified":null,"image":"1f485-1f3fc.png","sheet_x":23,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F485-1F3FD","non_qualified":null,"image":"1f485-1f3fd.png","sheet_x":23,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F485-1F3FE","non_qualified":null,"image":"1f485-1f3fe.png","sheet_x":23,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F485-1F3FF","non_qualified":null,"image":"1f485-1f3ff.png","sheet_x":23,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Nail Polish","b":"1F485","j":["beauty","manicure","finger","fashion","nail"],"k":[23,44]},"ear":{"skin_variations":{"1F3FB":{"unified":"1F442-1F3FB","non_qualified":null,"image":"1f442-1f3fb.png","sheet_x":13,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F442-1F3FC","non_qualified":null,"image":"1f442-1f3fc.png","sheet_x":13,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F442-1F3FD","non_qualified":null,"image":"1f442-1f3fd.png","sheet_x":13,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F442-1F3FE","non_qualified":null,"image":"1f442-1f3fe.png","sheet_x":13,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F442-1F3FF","non_qualified":null,"image":"1f442-1f3ff.png","sheet_x":13,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Ear","b":"1F442","j":["face","hear","sound","listen"],"k":[13,45]},"nose":{"skin_variations":{"1F3FB":{"unified":"1F443-1F3FB","non_qualified":null,"image":"1f443-1f3fb.png","sheet_x":14,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F443-1F3FC","non_qualified":null,"image":"1f443-1f3fc.png","sheet_x":14,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F443-1F3FD","non_qualified":null,"image":"1f443-1f3fd.png","sheet_x":14,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F443-1F3FE","non_qualified":null,"image":"1f443-1f3fe.png","sheet_x":14,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F443-1F3FF","non_qualified":null,"image":"1f443-1f3ff.png","sheet_x":14,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Nose","b":"1F443","j":["smell","sniff"],"k":[13,51]},"footprints":{"a":"Footprints","b":"1F463","j":["feet","tracking","walking","beach"],"k":[15,39]},"eyes":{"a":"Eyes","b":"1F440","j":["look","watch","stalk","peek","see"],"k":[13,42]},"eye":{"a":"Eye","b":"1F441-FE0F","c":"1F441","j":["face","look","see","watch","stare"],"k":[13,44],"o":7},"eye-in-speech-bubble":{"a":"Eye in Speech Bubble","b":"1F441-FE0F-200D-1F5E8-FE0F","k":[13,43],"o":7},"brain":{"a":"Brain","b":"1F9E0","k":[46,22],"o":10},"tongue":{"a":"Tongue","b":"1F445","j":["mouth","playful"],"k":[14,6]},"lips":{"a":"Mouth","b":"1F444","j":["mouth","kiss"],"k":[14,5]},"kiss":{"a":"Kiss Mark","b":"1F48B","j":["face","lips","love","like","affection","valentines"],"k":[24,37]},"cupid":{"a":"Heart with Arrow","b":"1F498","j":["love","like","heart","affection","valentines"],"k":[24,50]},"heart":{"a":"Heavy Black Heart","b":"2764-FE0F","c":"2764","j":["love","like","valentines"],"k":[50,8],"l":["<3"],"m":"<3","o":1},"heartbeat":{"a":"Beating Heart","b":"1F493","j":["love","like","affection","valentines","pink","heart"],"k":[24,45]},"broken_heart":{"a":"Broken Heart","b":"1F494","j":["sad","sorry","break","heart","heartbreak"],"k":[24,46],"l":[" Date: Mon, 15 Mar 2021 01:33:46 +0100 Subject: [PATCH 025/249] Update CHANGES.md to include the correct PR number --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index c6f98124d1..36ea9f5cfd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -41,7 +41,7 @@ Improvements 🙌: - Sending is now queuing by room and not uniquely to the session - Improve Snackbar duration (#2929) - Improve sending message state (#2937) - - Update reactions to Unicode 13.1 (#1481) + - Update reactions to Unicode 13.1 (#2998) Bugfix 🐛: - Try to fix crash about UrlPreview (#2640) From 51650fd8994d7ad9722153cf38a2004372237d49 Mon Sep 17 00:00:00 2001 From: oogm Date: Tue, 16 Mar 2021 16:23:06 +0100 Subject: [PATCH 026/249] Add script to pull emojis from Unicode.org as a file --- tools/import_emojis.py | 77 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tools/import_emojis.py diff --git a/tools/import_emojis.py b/tools/import_emojis.py new file mode 100644 index 0000000000..bdbade0e2d --- /dev/null +++ b/tools/import_emojis.py @@ -0,0 +1,77 @@ +import requests +import json +import re +from bs4 import BeautifulSoup + +# Create skeleton of the final json file as a python dictionary: +emoji_picker_datasource = { + "compressed": True, + "categories": [], + "emojis": {}, + "aliases": {} +} +emoji_picker_datasource_categories = emoji_picker_datasource["categories"] +emoji_picker_datasource_emojis = emoji_picker_datasource["emojis"] + + +# Get official emoji list from unicode.org (Emoji List, v13.1 at time of writing) +req = requests.get("https://unicode.org/emoji/charts/emoji-list.html") +soup = BeautifulSoup(req.content, 'html.parser') + +# Navigate to table +table = soup.body.table + +# Go over all rows +for row in table.find_all('tr'): + # Add "bigheads" rows to categories + if 'bighead' in next(row.children)['class']: + relevant_element = row.find('a') + category_id = relevant_element['name'] + category_name = relevant_element.text + emoji_picker_datasource_categories.append({ + "id": category_id, + "name": category_name, + "emojis": [] + }) + + # Add information in "rchars" rows to the last encountered category and emojis + if row.find('td', class_='code'): + # Get columns + cols = row.find_all('td') + no_element = cols[0] + code_element = cols[1] + sample_element = cols[2] + cldr_element = cols[3] + keywords_element = cols[4] + + # Extract information from columns + # Extract name and id + # => Remove spaces, colons and unicode-characters + emoji_name = cldr_element.text + emoji_id = cldr_element.text.lower() + emoji_id = re.sub(r'[^A-Za-z0-9 ]+', '', emoji_id, flags=re.UNICODE) # Only keep alphanumeric, space characters + emoji_id = emoji_id.strip() # Remove leading/trailing whitespaces + emoji_id = emoji_id.replace(' ', '-') + + # Extract emoji unicode-codepoint + emoji_code_raw = code_element.text + emoji_code_list = emoji_code_raw.split(" ") + emoji_code_list = [e[2:] for e in emoji_code_list] + emoji_code = "-".join(emoji_code_list) + + # Extract keywords + emoji_keywords = keywords_element.text.split(" | ") + + # Add the emoji-id to the last entry in "categories" + emoji_picker_datasource_categories[-1]["emojis"].append(emoji_id) + + # Add the emoji itself to the "emojis" dict + emoji_picker_datasource_emojis[emoji_id] = { + "a": emoji_name, + "b": emoji_code, + "j": emoji_keywords + } + +# Print result to file (overwrite previous), without escaping unicode characters +with open("../vector/src/main/res/raw/emoji_picker_datasource.json", "w") as outfile: + json.dump(emoji_picker_datasource, outfile, ensure_ascii=False) From f230830763a977a103c70a02e382ef51a6c797ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Mar 2021 07:07:15 +0000 Subject: [PATCH 027/249] Bump mockk-android from 1.10.6 to 1.11.0 Bumps [mockk-android](https://github.com/mockk/mockk) from 1.10.6 to 1.11.0. - [Release notes](https://github.com/mockk/mockk/releases) - [Commits](https://github.com/mockk/mockk/compare/1.10.6...1.11.0) Signed-off-by: dependabot[bot] --- matrix-sdk-android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index c9918cceaf..8e0f1ff269 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -186,7 +186,7 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'org.amshove.kluent:kluent-android:1.61' // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 - androidTestImplementation 'io.mockk:mockk-android:1.10.6' + androidTestImplementation 'io.mockk:mockk-android:1.11.0' androidTestImplementation "androidx.arch.core:core-testing:$arch_version" androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" // Plant Timber tree for test From 118ea4b0b2ff2f0d814943a1500b2e2cdecd7611 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 17 Mar 2021 07:08:12 +0000 Subject: [PATCH 028/249] Bump mockk from 1.10.6 to 1.11.0 Bumps [mockk](https://github.com/mockk/mockk) from 1.10.6 to 1.11.0. - [Release notes](https://github.com/mockk/mockk/releases) - [Commits](https://github.com/mockk/mockk/compare/1.10.6...1.11.0) Signed-off-by: dependabot[bot] --- matrix-sdk-android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index c9918cceaf..464dea8161 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -172,7 +172,7 @@ dependencies { testImplementation 'org.robolectric:robolectric:4.5.1' //testImplementation 'org.robolectric:shadows-support-v4:3.0' // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 - testImplementation 'io.mockk:mockk:1.10.6' + testImplementation 'io.mockk:mockk:1.11.0' testImplementation 'org.amshove.kluent:kluent-android:1.65' testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" // Plant Timber tree for test From 8e85d5515d133f8ab6b147d06e9dcebaabfeed8d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Mar 2021 07:18:33 +0000 Subject: [PATCH 029/249] Bump libphonenumber from 8.12.19 to 8.12.20 Bumps [libphonenumber](https://github.com/google/libphonenumber) from 8.12.19 to 8.12.20. - [Release notes](https://github.com/google/libphonenumber/releases) - [Changelog](https://github.com/google/libphonenumber/blob/master/making-metadata-changes.md) - [Commits](https://github.com/google/libphonenumber/compare/v8.12.19...v8.12.20) Signed-off-by: dependabot[bot] --- matrix-sdk-android/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index c9918cceaf..17d8fe59a0 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -166,7 +166,7 @@ dependencies { implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.19' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.20' testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.5.1' diff --git a/vector/build.gradle b/vector/build.gradle index 0468c68614..10fae97106 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -342,7 +342,7 @@ dependencies { implementation 'com.facebook.stetho:stetho:1.5.1' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.19' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.20' // rx implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0' From 7731c4a3bba5dd54a48d9920fbb5663f314d1079 Mon Sep 17 00:00:00 2001 From: Sven Grewe Date: Wed, 17 Mar 2021 19:30:30 +0000 Subject: [PATCH 030/249] Translated using Weblate (German) Currently translated at 99.9% (2361 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- vector/src/main/res/values-de/strings.xml | 197 +++++++++++----------- 1 file changed, 100 insertions(+), 97 deletions(-) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 51fe3937fd..034f83e40d 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 @@ -352,8 +352,8 @@ 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 @@ -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 @@ -2729,4 +2729,7 @@ 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 + Schlug fehl + Willst du zu sendende Nachrichten zurückziehen\? + Alle fehgeschlagene Nachrichten löschen \ No newline at end of file From 214ef23c73217b653bff18cd7950a5814ab54696 Mon Sep 17 00:00:00 2001 From: tiptoptom Date: Tue, 16 Mar 2021 14:41:29 +0000 Subject: [PATCH 031/249] Translated using Weblate (German) Currently translated at 99.9% (2361 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- vector/src/main/res/values-de/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 034f83e40d..5527262011 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -2732,4 +2732,5 @@ Schlug fehl Willst du zu sendende Nachrichten zurückziehen\? Alle fehgeschlagene Nachrichten löschen + Senden der Nachricht gescheitert \ No newline at end of file From 193db6a46c932345e99c9999aea00c40dc754f92 Mon Sep 17 00:00:00 2001 From: libexus Date: Tue, 16 Mar 2021 14:40:10 +0000 Subject: [PATCH 032/249] Translated using Weblate (German) Currently translated at 99.9% (2361 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- vector/src/main/res/values-de/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 5527262011..6ad2fe92f7 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -2733,4 +2733,5 @@ Willst du zu sendende Nachrichten zurückziehen\? Alle fehgeschlagene Nachrichten löschen Senden der Nachricht gescheitert + Wird gesendet \ No newline at end of file From 327088d79f3f597757f91648af4b80af34ddfd63 Mon Sep 17 00:00:00 2001 From: Hivaa Date: Mon, 15 Mar 2021 11:00:33 +0000 Subject: [PATCH 033/249] Translated using Weblate (Persian) Currently translated at 100.0% (2362 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- vector/src/main/res/values-fa/strings.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 From fcb30f619564998d6e9309cf7d0eb8add4af0380 Mon Sep 17 00:00:00 2001 From: Thibault Martin Date: Mon, 15 Mar 2021 19:38:07 +0000 Subject: [PATCH 034/249] Translated using Weblate (French) Currently translated at 100.0% (2362 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- vector/src/main/res/values-fr/strings.xml | 25 ++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 315e3fe978..57382fa9df 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. @@ -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 From ece3941e1f0c96c9e6200b7ac814b59a0c17a8ef Mon Sep 17 00:00:00 2001 From: GokdenizK Date: Wed, 17 Mar 2021 07:08:33 +0000 Subject: [PATCH 035/249] Translated using Weblate (Turkish) Currently translated at 64.8% (1531 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/tr/ --- vector/src/main/res/values-tr/strings.xml | 30 +++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/vector/src/main/res/values-tr/strings.xml b/vector/src/main/res/values-tr/strings.xml index 13d4a7c313..e5773a99a1 100644 --- a/vector/src/main/res/values-tr/strings.xml +++ b/vector/src/main/res/values-tr/strings.xml @@ -1712,4 +1712,34 @@ %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 \ No newline at end of file From b2441b3e55659f33a84e6dee984d6fa637a20f5f Mon Sep 17 00:00:00 2001 From: TyIL TTY7 Date: Wed, 17 Mar 2021 02:24:08 +0000 Subject: [PATCH 036/249] Translated using Weblate (Chinese (Simplified)) Currently translated at 94.3% (2228 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- vector/src/main/res/values-zh-rCN/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index e40e95da82..004e95019b 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -2471,4 +2471,9 @@ 未验证,缺少有效验证凭证 返回 系统默认 + • 匹配 %s 的服务器已被屏蔽。 + • IP 地址匹配的服务器已被屏蔽。 + • IP 地址符合的服务器已被允许。 + • 符合 %s 的服务器已被屏蔽。 + • 符合 %s 的服务器已被允许。 \ No newline at end of file From d31a4e1406dd16973f996cf3dcdb892c1feb10f2 Mon Sep 17 00:00:00 2001 From: KAHINA Date: Sun, 14 Mar 2021 22:35:00 +0000 Subject: [PATCH 037/249] Translated using Weblate (Kabyle) Currently translated at 92.2% (2179 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/kab/ --- vector/src/main/res/values-kab/strings.xml | 74 +++++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) 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 From a5b2083f1bd220ed19753b1fda0b4815c79e6062 Mon Sep 17 00:00:00 2001 From: Sven Grewe Date: Tue, 16 Mar 2021 15:12:57 +0000 Subject: [PATCH 038/249] Translated using Weblate (German) Currently translated at 100.0% (13 of 13 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/de/ --- fastlane/metadata/android/de/changelogs/40100110.txt | 2 +- fastlane/metadata/android/de/changelogs/40101010.txt | 2 ++ fastlane/metadata/android/de/full_description.txt | 12 ++++++------ fastlane/metadata/android/de/short_description.txt | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 fastlane/metadata/android/de/changelogs/40101010.txt 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. From 33306bf218565d12497875f74af7b17c0dd6deb7 Mon Sep 17 00:00:00 2001 From: Thibault Martin Date: Mon, 15 Mar 2021 18:21:27 +0000 Subject: [PATCH 039/249] Translated using Weblate (French) Currently translated at 100.0% (13 of 13 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fr/ --- fastlane/metadata/android/fr/changelogs/40100130.txt | 2 +- fastlane/metadata/android/fr/changelogs/40100140.txt | 2 ++ fastlane/metadata/android/fr/changelogs/40100150.txt | 2 ++ fastlane/metadata/android/fr/changelogs/40100160.txt | 2 ++ fastlane/metadata/android/fr/changelogs/40100170.txt | 2 ++ fastlane/metadata/android/fr/changelogs/40101000.txt | 2 ++ fastlane/metadata/android/fr/changelogs/40101010.txt | 2 ++ 7 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 fastlane/metadata/android/fr/changelogs/40100140.txt create mode 100644 fastlane/metadata/android/fr/changelogs/40100150.txt create mode 100644 fastlane/metadata/android/fr/changelogs/40100160.txt create mode 100644 fastlane/metadata/android/fr/changelogs/40100170.txt create mode 100644 fastlane/metadata/android/fr/changelogs/40101000.txt create mode 100644 fastlane/metadata/android/fr/changelogs/40101010.txt 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 From ab12c641a84a6a713054c1631b72c90073360555 Mon Sep 17 00:00:00 2001 From: KAHINA Date: Sun, 14 Mar 2021 22:23:36 +0000 Subject: [PATCH 040/249] Translated using Weblate (Kabyle) Currently translated at 23.0% (3 of 13 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/kab/ --- fastlane/metadata/android/kab/short_description.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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. From 3ceee8302af93f2bce03e28ecf4d0566abf093b2 Mon Sep 17 00:00:00 2001 From: GokdenizK Date: Wed, 17 Mar 2021 06:58:48 +0000 Subject: [PATCH 041/249] Translated using Weblate (Turkish) Currently translated at 76.9% (10 of 13 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/tr/ --- fastlane/metadata/android/tr/changelogs/40100140.txt | 2 ++ fastlane/metadata/android/tr/changelogs/40100170.txt | 2 ++ fastlane/metadata/android/tr/changelogs/40101000.txt | 2 ++ fastlane/metadata/android/tr/changelogs/40101010.txt | 2 ++ 4 files changed, 8 insertions(+) create mode 100644 fastlane/metadata/android/tr/changelogs/40100140.txt create mode 100644 fastlane/metadata/android/tr/changelogs/40100170.txt create mode 100644 fastlane/metadata/android/tr/changelogs/40101000.txt create mode 100644 fastlane/metadata/android/tr/changelogs/40101010.txt 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 From 743a9cf358878687826109bb72912c30a5aa5f4d Mon Sep 17 00:00:00 2001 From: Graeme Power Date: Sun, 14 Mar 2021 16:51:44 +0000 Subject: [PATCH 042/249] Translated using Weblate (Irish) Currently translated at 5.6% (133 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ga/ --- vector/src/main/res/values-ga/strings.xml | 165 +++++++++++++++++++++- 1 file changed, 164 insertions(+), 1 deletion(-) diff --git a/vector/src/main/res/values-ga/strings.xml b/vector/src/main/res/values-ga/strings.xml index a6b3daec93..ee84da7b92 100644 --- a/vector/src/main/res/values-ga/strings.xml +++ b/vector/src/main/res/values-ga/strings.xml @@ -1,2 +1,165 @@ - \ No newline at end of file + + 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 From 64bdd894d6b83d94029ca2c14fbe21e8c01ffcb0 Mon Sep 17 00:00:00 2001 From: Graeme Power Date: Sun, 14 Mar 2021 16:53:48 +0000 Subject: [PATCH 043/249] Translated using Weblate (Irish) Currently translated at 7.6% (1 of 13 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/ga/ --- fastlane/metadata/android/ga/title.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/ga/title.txt 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) From 3078adf0da4bca1a1484554851462a8747d2d661 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 18 Mar 2021 10:40:33 +0100 Subject: [PATCH 044/249] Version++ --- CHANGES.md | 27 +++++++++++++++++++++++++++ vector/build.gradle | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index d410a5033d..cad62d0851 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,30 @@ +Changes in Element 1.1.4 (2021-XX-XX) +=================================================== + +Features ✨: + - + +Improvements 🙌: + - + +Bugfix 🐛: + - + +Translations 🗣: + - + +SDK API changes ⚠️: + - + +Build 🧱: + - + +Test: + - + +Other changes: + - + Changes in Element 1.1.3 (2021-03-18) =================================================== diff --git a/vector/build.gradle b/vector/build.gradle index 0468c68614..008a165aae 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 = 3 +ext.versionPatch = 4 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' From 9946ba8aa4bdf4bd9b5f27041641fdd3c59667d6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Mar 2021 11:07:31 +0100 Subject: [PATCH 045/249] Split network request `/keys/query` into smaller requests (250 users max) (#2925) --- CHANGES.md | 2 +- .../crypto/tasks/DownloadKeysForUsersTask.kt | 63 ++++++++++++++++--- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index cad62d0851..594ac8e84d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ Features ✨: - Improvements 🙌: - - + - Split network request `/keys/query` into smaller requests (250 users max) (#2925) Bugfix 🐛: - 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 5eb24b116a..c78a5cb74c 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 @@ -16,13 +16,21 @@ package org.matrix.android.sdk.internal.crypto.tasks +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import org.matrix.android.sdk.internal.crypto.api.CryptoApi +import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeysWithUnsigned import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryBody import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse +import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject +import kotlin.math.ceil internal interface DownloadKeysForUsersTask : Task { data class Params( @@ -39,15 +47,56 @@ internal class DefaultDownloadKeysForUsers @Inject constructor( ) : DownloadKeysForUsersTask { override suspend fun execute(params: DownloadKeysForUsersTask.Params): KeysQueryResponse { - val downloadQuery = params.userIds.associateWith { emptyList() } + val numberOfChunks = ceil(params.userIds.size / LIMIT.toDouble()).toInt().coerceAtLeast(1) + val chunkSize = params.userIds.size / numberOfChunks - val body = KeysQueryBody( - deviceKeys = downloadQuery, - token = params.token?.takeIf { it.isNotEmpty() } - ) + // Store server results in these mutable maps + val deviceKeys = mutableMapOf>() + val failures = mutableMapOf>() + val masterKeys = mutableMapOf() + val selfSigningKeys = mutableMapOf() + val userSigningKeys = mutableMapOf() - return executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.downloadKeysForUsers(body) + val mutex = Mutex() + + // Split network request into smaller request (#2925) + coroutineScope { + params.userIds + .chunked(chunkSize) + .map { + KeysQueryBody( + deviceKeys = it.associateWith { emptyList() }, + token = params.token?.takeIf { token -> token.isNotEmpty() } + ) + } + .map { body -> + async { + val result = executeRequest(globalErrorReceiver) { + apiCall = cryptoApi.downloadKeysForUsers(body) + } + + mutex.withLock { + deviceKeys.putAll(result.deviceKeys.orEmpty()) + failures.putAll(result.failures.orEmpty()) + masterKeys.putAll(result.masterKeys.orEmpty()) + selfSigningKeys.putAll(result.selfSigningKeys.orEmpty()) + userSigningKeys.putAll(result.userSigningKeys.orEmpty()) + } + } + } + .joinAll() } + + return KeysQueryResponse( + deviceKeys = deviceKeys, + failures = failures, + masterKeys = masterKeys, + selfSigningKeys = selfSigningKeys, + userSigningKeys = userSigningKeys + ) + } + + companion object { + const val LIMIT = 250 } } From 103ba463c38ec5532f8ebb28d09897db237d2f69 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Mar 2021 11:34:27 +0100 Subject: [PATCH 046/249] Create getBetsChunkSize to avoid code duplication --- .../crypto/tasks/DownloadKeysForUsersTask.kt | 9 +-- .../internal/session/sync/RoomSyncHandler.kt | 18 ++--- .../android/sdk/internal/util/MathUtils.kt | 43 +++++++++++ .../android/sdk/internal/util/MathUtilTest.kt | 73 +++++++++++++++++++ 4 files changed, 129 insertions(+), 14 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt 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 c78a5cb74c..3f1f5299d0 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 @@ -29,12 +29,12 @@ import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.getBetsChunkSize import javax.inject.Inject -import kotlin.math.ceil internal interface DownloadKeysForUsersTask : Task { data class Params( - // the list of users to get keys for. + // the list of users to get keys for. The list MUST NOT be empty val userIds: List, // the up-to token val token: String? @@ -47,8 +47,7 @@ internal class DefaultDownloadKeysForUsers @Inject constructor( ) : DownloadKeysForUsersTask { override suspend fun execute(params: DownloadKeysForUsersTask.Params): KeysQueryResponse { - val numberOfChunks = ceil(params.userIds.size / LIMIT.toDouble()).toInt().coerceAtLeast(1) - val chunkSize = params.userIds.size / numberOfChunks + val bestChunkSize = getBetsChunkSize(params.userIds.size, LIMIT) // Store server results in these mutable maps val deviceKeys = mutableMapOf>() @@ -62,7 +61,7 @@ internal class DefaultDownloadKeysForUsers @Inject constructor( // Split network request into smaller request (#2925) coroutineScope { params.userIds - .chunked(chunkSize) + .chunked(bestChunkSize.chunkSize) .map { KeysQueryBody( deviceKeys = it.associateWith { emptyList() }, 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 0f97d0cb39..4553ff90ed 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 @@ -64,9 +64,9 @@ import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral import org.matrix.android.sdk.internal.session.sync.model.RoomSync import org.matrix.android.sdk.internal.session.sync.model.RoomSyncAccountData import org.matrix.android.sdk.internal.session.sync.model.RoomsSyncResponse +import org.matrix.android.sdk.internal.util.getBetsChunkSize import timber.log.Timber import javax.inject.Inject -import kotlin.math.ceil internal class RoomSyncHandler @Inject constructor(private val readReceiptHandler: ReadReceiptHandler, private val roomSummaryUpdater: RoomSummaryUpdater, @@ -140,17 +140,17 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle syncLocalTimeStampMillis: Long, aggregator: SyncResponsePostTreatmentAggregator, reporter: ProgressReporter?) { - val maxSize = (initialSyncStrategy as? InitialSyncStrategy.Optimized)?.maxRoomsToInsert ?: Int.MAX_VALUE - val listSize = handlingStrategy.data.keys.size - val numberOfChunks = ceil(listSize / maxSize.toDouble()).toInt() + val bestChunkSize = getBetsChunkSize( + listSize = handlingStrategy.data.keys.size, + limit = (initialSyncStrategy as? InitialSyncStrategy.Optimized)?.maxRoomsToInsert ?: Int.MAX_VALUE + ) - if (numberOfChunks > 1) { - reportSubtask(reporter, InitSyncStep.ImportingAccountJoinedRooms, numberOfChunks, 0.6f) { - val chunkSize = listSize / numberOfChunks - Timber.d("INIT_SYNC $listSize rooms to insert, split into $numberOfChunks sublists of $chunkSize items") + if (bestChunkSize.shouldSplit()) { + reportSubtask(reporter, InitSyncStep.ImportingAccountJoinedRooms, bestChunkSize.numberOfChunks, 0.6f) { + Timber.d("INIT_SYNC ${handlingStrategy.data.keys.size} rooms to insert, split with $bestChunkSize") // I cannot find a better way to chunk a map, so chunk the keys and then create new maps handlingStrategy.data.keys - .chunked(chunkSize) + .chunked(bestChunkSize.chunkSize) .forEachIndexed { index, roomIds -> val roomEntities = roomIds .also { Timber.d("INIT_SYNC insert ${roomIds.size} rooms") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt new file mode 100644 index 0000000000..6abf917ab0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt @@ -0,0 +1,43 @@ +/* + * 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.util + +import kotlin.math.ceil + +internal data class BestChunkSize( + val numberOfChunks: Int, + val chunkSize: Int +) { + fun shouldSplit() = numberOfChunks > 1 +} + +internal fun getBetsChunkSize(listSize: Int, limit: Int): BestChunkSize { + return if (listSize <= limit) { + BestChunkSize( + numberOfChunks = 1, + chunkSize = listSize + ) + } else { + val numberOfChunks = ceil(listSize / limit.toDouble()).toInt() + val chunkSize = listSize / numberOfChunks + + BestChunkSize( + numberOfChunks = numberOfChunks, + chunkSize = chunkSize + ) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt new file mode 100644 index 0000000000..7cb355a621 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2020 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.util + +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldHaveSize +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runners.MethodSorters +import org.matrix.android.sdk.MatrixTest + +@FixMethodOrder(MethodSorters.JVM) +class MathUtilTest : MatrixTest { + + @Test + fun testGetBestChunkSize0() = doTest(0, 100, 1, 0) + + @Test + fun testGetBestChunkSize1() = doTest(1, 100, 1, 1) + + @Test + fun testGetBestChunkSize5() = doTest(5, 100, 1, 5) + + @Test + fun testGetBestChunkSize99() = doTest(99, 100, 1, 99) + + @Test + fun testGetBestChunkSize100() = doTest(100, 100, 1, 100) + + @Test + fun testGetBestChunkSize101() = doTest(101, 100, 2, 50) + + @Test + fun testGetBestChunkSize199() = doTest(199, 100, 2, 99) + + @Test + fun testGetBestChunkSize200() = doTest(200, 100, 2, 100) + + @Test + fun testGetBestChunkSize201() = doTest(201, 100, 3, 67) + + @Test + fun testGetBestChunkSize240() = doTest(240, 100, 3, 80) + + private fun doTest(listSize: Int, limit: Int, expectedNumberOfChunks: Int, expectedChunkSize: Int) { + val result = getBetsChunkSize(listSize, limit) + + result.numberOfChunks shouldBeEqualTo expectedNumberOfChunks + result.chunkSize shouldBeEqualTo expectedChunkSize + + // Test that the result make sense, when we use chunked() + if (result.chunkSize > 0) { + generateSequence { "a" } + .take(listSize) + .chunked(result.chunkSize) + .shouldHaveSize(result.numberOfChunks) + } + } +} From da9f0c66671d4c964dcc43833d68ade797aa690f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Mar 2021 12:03:01 +0100 Subject: [PATCH 047/249] Fix an issue discovered by unit test --- .../java/org/matrix/android/sdk/internal/util/MathUtils.kt | 3 ++- .../java/org/matrix/android/sdk/internal/util/MathUtilTest.kt | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt index 6abf917ab0..0e18b62acd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt @@ -33,7 +33,8 @@ internal fun getBetsChunkSize(listSize: Int, limit: Int): BestChunkSize { ) } else { val numberOfChunks = ceil(listSize / limit.toDouble()).toInt() - val chunkSize = listSize / numberOfChunks + // Round on next Int + val chunkSize = ceil(listSize / numberOfChunks.toDouble()).toInt() BestChunkSize( numberOfChunks = numberOfChunks, diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt index 7cb355a621..f684d93310 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt @@ -42,10 +42,10 @@ class MathUtilTest : MatrixTest { fun testGetBestChunkSize100() = doTest(100, 100, 1, 100) @Test - fun testGetBestChunkSize101() = doTest(101, 100, 2, 50) + fun testGetBestChunkSize101() = doTest(101, 100, 2, 51) @Test - fun testGetBestChunkSize199() = doTest(199, 100, 2, 99) + fun testGetBestChunkSize199() = doTest(199, 100, 2, 100) @Test fun testGetBestChunkSize200() = doTest(200, 100, 2, 100) From f6032da7885efca70074617d235ac5aade5d91e0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Mar 2021 12:05:36 +0100 Subject: [PATCH 048/249] Add more test --- .../matrix/android/sdk/internal/util/MathUtilTest.kt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt index f684d93310..3abb3db6ca 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt @@ -30,13 +30,11 @@ class MathUtilTest : MatrixTest { fun testGetBestChunkSize0() = doTest(0, 100, 1, 0) @Test - fun testGetBestChunkSize1() = doTest(1, 100, 1, 1) - - @Test - fun testGetBestChunkSize5() = doTest(5, 100, 1, 5) - - @Test - fun testGetBestChunkSize99() = doTest(99, 100, 1, 99) + fun testGetBestChunkSize1to99() { + for (i in 1..99) { + doTest(i, 100, 1, i) + } + } @Test fun testGetBestChunkSize100() = doTest(100, 100, 1, 100) From 96b37a8206f8518924e059f428ee9202d4292dc6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 18 Mar 2021 10:57:28 +0100 Subject: [PATCH 049/249] Fix typo --- .../crypto/tasks/DownloadKeysForUsersTask.kt | 4 ++-- .../internal/session/sync/RoomSyncHandler.kt | 6 +++--- .../android/sdk/internal/util/MathUtils.kt | 4 ++-- .../android/sdk/internal/util/MathUtilTest.kt | 18 +++++++++--------- 4 files changed, 16 insertions(+), 16 deletions(-) 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 3f1f5299d0..ccf374ad7c 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 @@ -29,7 +29,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task -import org.matrix.android.sdk.internal.util.getBetsChunkSize +import org.matrix.android.sdk.internal.util.computeBestChunkSize import javax.inject.Inject internal interface DownloadKeysForUsersTask : Task { @@ -47,7 +47,7 @@ internal class DefaultDownloadKeysForUsers @Inject constructor( ) : DownloadKeysForUsersTask { override suspend fun execute(params: DownloadKeysForUsersTask.Params): KeysQueryResponse { - val bestChunkSize = getBetsChunkSize(params.userIds.size, LIMIT) + val bestChunkSize = computeBestChunkSize(params.userIds.size, LIMIT) // Store server results in these mutable maps val deviceKeys = mutableMapOf>() 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 4553ff90ed..e938d54903 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 @@ -64,7 +64,7 @@ import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral import org.matrix.android.sdk.internal.session.sync.model.RoomSync import org.matrix.android.sdk.internal.session.sync.model.RoomSyncAccountData import org.matrix.android.sdk.internal.session.sync.model.RoomsSyncResponse -import org.matrix.android.sdk.internal.util.getBetsChunkSize +import org.matrix.android.sdk.internal.util.computeBestChunkSize import timber.log.Timber import javax.inject.Inject @@ -140,12 +140,12 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle syncLocalTimeStampMillis: Long, aggregator: SyncResponsePostTreatmentAggregator, reporter: ProgressReporter?) { - val bestChunkSize = getBetsChunkSize( + val bestChunkSize = computeBestChunkSize( listSize = handlingStrategy.data.keys.size, limit = (initialSyncStrategy as? InitialSyncStrategy.Optimized)?.maxRoomsToInsert ?: Int.MAX_VALUE ) - if (bestChunkSize.shouldSplit()) { + if (bestChunkSize.shouldChunk()) { reportSubtask(reporter, InitSyncStep.ImportingAccountJoinedRooms, bestChunkSize.numberOfChunks, 0.6f) { Timber.d("INIT_SYNC ${handlingStrategy.data.keys.size} rooms to insert, split with $bestChunkSize") // I cannot find a better way to chunk a map, so chunk the keys and then create new maps diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt index 0e18b62acd..c9c597e93d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt @@ -22,10 +22,10 @@ internal data class BestChunkSize( val numberOfChunks: Int, val chunkSize: Int ) { - fun shouldSplit() = numberOfChunks > 1 + fun shouldChunk() = numberOfChunks > 1 } -internal fun getBetsChunkSize(listSize: Int, limit: Int): BestChunkSize { +internal fun computeBestChunkSize(listSize: Int, limit: Int): BestChunkSize { return if (listSize <= limit) { BestChunkSize( numberOfChunks = 1, diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt index 3abb3db6ca..ade811f9b7 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt @@ -27,35 +27,35 @@ import org.matrix.android.sdk.MatrixTest class MathUtilTest : MatrixTest { @Test - fun testGetBestChunkSize0() = doTest(0, 100, 1, 0) + fun testComputeBestChunkSize0() = doTest(0, 100, 1, 0) @Test - fun testGetBestChunkSize1to99() { + fun testComputeBestChunkSize1to99() { for (i in 1..99) { doTest(i, 100, 1, i) } } @Test - fun testGetBestChunkSize100() = doTest(100, 100, 1, 100) + fun testComputeBestChunkSize100() = doTest(100, 100, 1, 100) @Test - fun testGetBestChunkSize101() = doTest(101, 100, 2, 51) + fun testComputeBestChunkSize101() = doTest(101, 100, 2, 51) @Test - fun testGetBestChunkSize199() = doTest(199, 100, 2, 100) + fun testComputeBestChunkSize199() = doTest(199, 100, 2, 100) @Test - fun testGetBestChunkSize200() = doTest(200, 100, 2, 100) + fun testComputeBestChunkSize200() = doTest(200, 100, 2, 100) @Test - fun testGetBestChunkSize201() = doTest(201, 100, 3, 67) + fun testComputeBestChunkSize201() = doTest(201, 100, 3, 67) @Test - fun testGetBestChunkSize240() = doTest(240, 100, 3, 80) + fun testComputeBestChunkSize240() = doTest(240, 100, 3, 80) private fun doTest(listSize: Int, limit: Int, expectedNumberOfChunks: Int, expectedChunkSize: Int) { - val result = getBetsChunkSize(listSize, limit) + val result = computeBestChunkSize(listSize, limit) result.numberOfChunks shouldBeEqualTo expectedNumberOfChunks result.chunkSize shouldBeEqualTo expectedChunkSize From dbff5015df3d4ce43627975f918c199dddb04215 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 18 Mar 2021 11:56:19 +0100 Subject: [PATCH 050/249] Keep is simple if there is no need to chunk --- .../crypto/tasks/DownloadKeysForUsersTask.kt | 89 +++++++++++-------- 1 file changed, 51 insertions(+), 38 deletions(-) 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 ccf374ad7c..0c17cbb43a 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 @@ -48,51 +48,64 @@ internal class DefaultDownloadKeysForUsers @Inject constructor( override suspend fun execute(params: DownloadKeysForUsersTask.Params): KeysQueryResponse { val bestChunkSize = computeBestChunkSize(params.userIds.size, LIMIT) + val token = params.token?.takeIf { token -> token.isNotEmpty() } - // Store server results in these mutable maps - val deviceKeys = mutableMapOf>() - val failures = mutableMapOf>() - val masterKeys = mutableMapOf() - val selfSigningKeys = mutableMapOf() - val userSigningKeys = mutableMapOf() + return if (bestChunkSize.shouldChunk()) { + // Store server results in these mutable maps + val deviceKeys = mutableMapOf>() + val failures = mutableMapOf>() + val masterKeys = mutableMapOf() + val selfSigningKeys = mutableMapOf() + val userSigningKeys = mutableMapOf() - val mutex = Mutex() + val mutex = Mutex() - // Split network request into smaller request (#2925) - coroutineScope { - params.userIds - .chunked(bestChunkSize.chunkSize) - .map { - KeysQueryBody( - deviceKeys = it.associateWith { emptyList() }, - token = params.token?.takeIf { token -> token.isNotEmpty() } - ) - } - .map { body -> - async { - val result = executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.downloadKeysForUsers(body) - } + // Split network request into smaller request (#2925) + coroutineScope { + params.userIds + .chunked(bestChunkSize.chunkSize) + .map { + KeysQueryBody( + deviceKeys = it.associateWith { emptyList() }, + token = token + ) + } + .map { body -> + async { + val result = executeRequest(globalErrorReceiver) { + apiCall = cryptoApi.downloadKeysForUsers(body) + } - mutex.withLock { - deviceKeys.putAll(result.deviceKeys.orEmpty()) - failures.putAll(result.failures.orEmpty()) - masterKeys.putAll(result.masterKeys.orEmpty()) - selfSigningKeys.putAll(result.selfSigningKeys.orEmpty()) - userSigningKeys.putAll(result.userSigningKeys.orEmpty()) + mutex.withLock { + deviceKeys.putAll(result.deviceKeys.orEmpty()) + failures.putAll(result.failures.orEmpty()) + masterKeys.putAll(result.masterKeys.orEmpty()) + selfSigningKeys.putAll(result.selfSigningKeys.orEmpty()) + userSigningKeys.putAll(result.userSigningKeys.orEmpty()) + } } } - } - .joinAll() - } + .joinAll() + } - return KeysQueryResponse( - deviceKeys = deviceKeys, - failures = failures, - masterKeys = masterKeys, - selfSigningKeys = selfSigningKeys, - userSigningKeys = userSigningKeys - ) + KeysQueryResponse( + deviceKeys = deviceKeys, + failures = failures, + masterKeys = masterKeys, + selfSigningKeys = selfSigningKeys, + userSigningKeys = userSigningKeys + ) + } else { + // No need to chunk, direct request + executeRequest(globalErrorReceiver) { + apiCall = cryptoApi.downloadKeysForUsers( + KeysQueryBody( + deviceKeys = params.userIds.associateWith { emptyList() }, + token = token + ) + ) + } + } } companion object { From 973e111dad86144026a55ba3e69793f5d1c76471 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Mar 2021 12:12:17 +0000 Subject: [PATCH 051/249] Bump epoxy_version from 4.4.2 to 4.4.3 Bumps `epoxy_version` from 4.4.2 to 4.4.3. Updates `epoxy` from 4.4.2 to 4.4.3 - [Release notes](https://github.com/airbnb/epoxy/releases) - [Changelog](https://github.com/airbnb/epoxy/blob/master/CHANGELOG.md) - [Commits](https://github.com/airbnb/epoxy/commits) Updates `epoxy-glide-preloading` from 4.4.2 to 4.4.3 - [Release notes](https://github.com/airbnb/epoxy/releases) - [Changelog](https://github.com/airbnb/epoxy/blob/master/CHANGELOG.md) - [Commits](https://github.com/airbnb/epoxy/commits) Updates `epoxy-processor` from 4.4.2 to 4.4.3 - [Release notes](https://github.com/airbnb/epoxy/releases) - [Changelog](https://github.com/airbnb/epoxy/blob/master/CHANGELOG.md) - [Commits](https://github.com/airbnb/epoxy/commits) Updates `epoxy-paging` from 4.4.2 to 4.4.3 - [Release notes](https://github.com/airbnb/epoxy/releases) - [Changelog](https://github.com/airbnb/epoxy/blob/master/CHANGELOG.md) - [Commits](https://github.com/airbnb/epoxy/commits) Signed-off-by: dependabot[bot] --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index ab3d2dc449..70538b100a 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -290,7 +290,7 @@ android { dependencies { - def epoxy_version = '4.4.2' + def epoxy_version = '4.4.3' def fragment_version = '1.3.0' def arrow_version = "0.8.2" def markwon_version = '4.1.2' From 35635c859d973aa55d0c18933ad03bae753388e7 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 18 Mar 2021 15:08:56 +0100 Subject: [PATCH 052/249] Send several NO_OLM in one request --- CHANGES.md | 2 +- .../algorithms/megolm/MXMegolmEncryption.kt | 39 +++++++++++-------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index cad62d0851..c7f214e1c0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ Features ✨: - Improvements 🙌: - - + - Crypto improvement | Bulck send NO_OLM withheld code Bugfix 🐛: - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index 6b91c0b859..cf13f0e789 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -67,8 +67,9 @@ internal class MXMegolmEncryption( init { // restore existing outbound session if any - outboundSession = olmDevice.restoreOutboundGroupSessionForRoom(roomId) + outboundSession = olmDevice.restoreOutboundGroupSessionForRoom(roomId) } + // Default rotation periods // TODO: Make it configurable via parameters // Session rotation periods @@ -125,6 +126,7 @@ internal class MXMegolmEncryption( Timber.v("## CRYPTO | preshareKey ${System.currentTimeMillis() - ts} millis") } + /** * Prepare a new session. * @@ -240,6 +242,7 @@ internal class MXMegolmEncryption( val contentMap = MXUsersDevicesMap() var haveTargets = false val userIds = results.userIds + val noOlmToNotify = mutableListOf() for (userId in userIds) { val devicesToShareWith = devicesByUser[userId] for ((deviceID) in devicesToShareWith!!) { @@ -251,13 +254,7 @@ internal class MXMegolmEncryption( // MSC 2399 // send withheld m.no_olm: an olm session could not be established. // This may happen, for example, if the sender was unable to obtain a one-time key from the recipient. - notifyKeyWithHeld( - listOf(UserDevice(userId, deviceID)), - session.sessionId, - olmDevice.deviceCurve25519Key, - WithHeldCode.NO_OLM - ) - + noOlmToNotify.add(UserDevice(userId, deviceID)) continue } Timber.i("## CRYPTO | shareUserDevicesKey() : Add to share keys contentMap for $userId:$deviceID") @@ -277,14 +274,14 @@ internal class MXMegolmEncryption( session.sharedWithHelper.markedSessionAsShared(userId, deviceId, chainIndex) gossipingEventBuffer.add( Event( - type = EventType.ROOM_KEY, - senderId = this.userId, - content = submap.apply { - this["session_key"] = "" - // we add a fake key for trail - this["_dest"] = "$userId|$deviceId" - } - )) + type = EventType.ROOM_KEY, + senderId = this.userId, + content = submap.apply { + this["session_key"] = "" + // we add a fake key for trail + this["_dest"] = "$userId|$deviceId" + } + )) } } @@ -304,6 +301,16 @@ internal class MXMegolmEncryption( } else { Timber.i("## CRYPTO | shareUserDevicesKey() : no need to sharekey") } + + if (noOlmToNotify.isNotEmpty()) { + //XXX offload?, as they won't read the message anyhow? + notifyKeyWithHeld( + noOlmToNotify, + session.sessionId, + olmDevice.deviceCurve25519Key, + WithHeldCode.NO_OLM + ) + } } private suspend fun notifyKeyWithHeld(targets: List, From 9f475989509984e2cc84e9c88527456885249cd4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 18 Mar 2021 18:01:21 +0100 Subject: [PATCH 053/249] Display the room shield in all room setting screens --- CHANGES.md | 2 +- .../app/features/roomprofile/alias/RoomAliasFragment.kt | 1 + .../roomprofile/banned/RoomBannedMemberListFragment.kt | 1 + .../roomprofile/members/RoomMemberListFragment.kt | 1 + .../roomprofile/permissions/RoomPermissionsFragment.kt | 1 + .../roomprofile/settings/RoomSettingsFragment.kt | 1 + .../main/res/layout/fragment_room_setting_generic.xml | 9 +++++++++ 7 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index cad62d0851..e559879648 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ Features ✨: - Improvements 🙌: - - + - Display the room shield in all room setting screens Bugfix 🐛: - diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt index 2fc1575341..1c5f8fe3af 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/alias/RoomAliasFragment.kt @@ -128,6 +128,7 @@ class RoomAliasFragment @Inject constructor( state.roomSummary()?.let { views.roomSettingsToolbarTitleView.text = it.displayName avatarRenderer.render(it.toMatrixItem(), views.roomSettingsToolbarAvatarImageView) + views.roomSettingsDecorationToolbarAvatarImageView.render(it.roomEncryptionTrustLevel) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt index 556d950230..620d65781c 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt @@ -117,6 +117,7 @@ class RoomBannedMemberListFragment @Inject constructor( state.roomSummary()?.let { views.roomSettingsToolbarTitleView.text = it.displayName avatarRenderer.render(it.toMatrixItem(), views.roomSettingsToolbarAvatarImageView) + views.roomSettingsDecorationToolbarAvatarImageView.render(it.roomEncryptionTrustLevel) } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt index 1db9200451..2ff89d6e54 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt @@ -140,6 +140,7 @@ class RoomMemberListFragment @Inject constructor( state.roomSummary()?.let { views.roomSettingGeneric.roomSettingsToolbarTitleView.text = it.displayName avatarRenderer.render(it.toMatrixItem(), views.roomSettingGeneric.roomSettingsToolbarAvatarImageView) + views.roomSettingGeneric.roomSettingsDecorationToolbarAvatarImageView.render(it.roomEncryptionTrustLevel) } } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt index 61635c9b31..a538c9269b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/permissions/RoomPermissionsFragment.kt @@ -91,6 +91,7 @@ class RoomPermissionsFragment @Inject constructor( state.roomSummary()?.let { views.roomSettingsToolbarTitleView.text = it.displayName avatarRenderer.render(it.toMatrixItem(), views.roomSettingsToolbarAvatarImageView) + views.roomSettingsDecorationToolbarAvatarImageView.render(it.roomEncryptionTrustLevel) } } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index e431dbfcd6..129888ee04 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -154,6 +154,7 @@ class RoomSettingsFragment @Inject constructor( state.roomSummary()?.let { views.roomSettingsToolbarTitleView.text = it.displayName avatarRenderer.render(it.toMatrixItem(), views.roomSettingsToolbarAvatarImageView) + views.roomSettingsDecorationToolbarAvatarImageView.render(it.roomEncryptionTrustLevel) } invalidateOptionsMenu() diff --git a/vector/src/main/res/layout/fragment_room_setting_generic.xml b/vector/src/main/res/layout/fragment_room_setting_generic.xml index c11ce07062..9c3827f919 100644 --- a/vector/src/main/res/layout/fragment_room_setting_generic.xml +++ b/vector/src/main/res/layout/fragment_room_setting_generic.xml @@ -34,6 +34,15 @@ app:layout_constraintTop_toTopOf="parent" tools:src="@tools:sample/avatars" /> + + Date: Thu, 18 Mar 2021 19:18:12 +0100 Subject: [PATCH 054/249] Remove roomId from DevTools item (copy/paste error) --- .../im/vector/app/features/roomprofile/RoomProfileController.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt index bb7d041199..10e6dceebe 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt @@ -222,7 +222,6 @@ class RoomProfileController @Inject constructor( buildProfileAction( id = "devTools", title = stringProvider.getString(R.string.dev_tools_menu_name), - subtitle = roomSummary.roomId, dividerColor = dividerColor, divider = false, editable = true, From ec50f891a29e80d931af1664f3433197959c4295 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 18 Mar 2021 19:22:29 +0100 Subject: [PATCH 055/249] Improve description format --- .../IncomingVerificationRequestHandler.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) 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 48f3f0a460..fc526b5322 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 @@ -119,13 +119,18 @@ 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) { - val user = session?.getUser(pr.otherUserId) + val user = session?.getUser(pr.otherUserId)?.toMatrixItem() val name = user?.getBestName() ?: pr.otherUserId + val description = if (name == pr.otherUserId) { + name + } else { + "$name (${pr.otherUserId})" + } val alert = VerificationVectorAlert( uniqueIdForVerificationRequest(pr), context.getString(R.string.sas_incoming_request_notif_title), - "$name(${pr.otherUserId})", + description, R.drawable.ic_shield_black, shouldBeDisplayedIn = { activity -> if (activity is RoomDetailActivity) { @@ -136,7 +141,7 @@ class IncomingVerificationRequestHandler @Inject constructor( } ) .apply { - viewBinder = VerificationVectorAlert.ViewBinder(user?.toMatrixItem(), avatarRenderer.get()) + viewBinder = VerificationVectorAlert.ViewBinder(user, avatarRenderer.get()) contentAction = Runnable { (weakCurrentActivity?.get() as? VectorBaseActivity<*>)?.let { val roomId = pr.roomId From 92f1390407dac3fc78cbd51b9c00b858c057e298 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Mar 2021 07:11:14 +0000 Subject: [PATCH 056/249] Bump gradle from 4.1.2 to 4.1.3 Bumps gradle from 4.1.2 to 4.1.3. Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ec7ec8c1de..4e2c81a379 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:4.1.2' + classpath 'com.android.tools.build:gradle:4.1.3' 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' From cfefde0c06f3b1baa354a81c0075a5bc032097ab Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 19 Mar 2021 10:23:23 +0100 Subject: [PATCH 057/249] Update CHANGES.md --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index c7f214e1c0..8e302599ac 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ Features ✨: - Improvements 🙌: - - Crypto improvement | Bulck send NO_OLM withheld code + - Crypto improvement | Bulk send NO_OLM withheld code Bugfix 🐛: - From 2d75c67aa39a0267ac9886f3479efe76969074a1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 19 Mar 2021 10:24:40 +0100 Subject: [PATCH 058/249] ktlint --- .../sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index cf13f0e789..697711d051 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -303,7 +303,7 @@ internal class MXMegolmEncryption( } if (noOlmToNotify.isNotEmpty()) { - //XXX offload?, as they won't read the message anyhow? + // XXX offload?, as they won't read the message anyhow? notifyKeyWithHeld( noOlmToNotify, session.sessionId, From 80db39a93449902f1d610f17014bac3e08063dd4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 19 Mar 2021 13:15:13 +0100 Subject: [PATCH 059/249] No alert possible in SignedOutActivity --- .../main/java/im/vector/app/features/popup/PopupAlertManager.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt index 67ef0514f2..3228ffa6e1 100644 --- a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt +++ b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt @@ -26,6 +26,7 @@ import im.vector.app.R import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.utils.isAnimationDisabled import im.vector.app.features.pin.PinActivity +import im.vector.app.features.signout.hard.SignedOutActivity import im.vector.app.features.themes.ThemeUtils import timber.log.Timber import java.lang.ref.WeakReference @@ -294,6 +295,7 @@ class PopupAlertManager @Inject constructor() { private fun shouldBeDisplayedIn(alert: VectorAlert?, activity: Activity): Boolean { return alert != null && activity !is PinActivity + && activity !is SignedOutActivity && activity is VectorBaseActivity<*> && alert.shouldBeDisplayedIn.invoke(activity) } From 3b1635130843d8f64d65d0bc79af6ee8d31e17f9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 19 Mar 2021 13:36:24 +0100 Subject: [PATCH 060/249] Fix bad theme change for the MainActivity --- CHANGES.md | 2 +- vector/src/main/java/im/vector/app/features/MainActivity.kt | 3 +++ .../im/vector/app/features/themes/ActivityOtherThemes.kt | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index e559879648..c3fde19bc4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,7 @@ Improvements 🙌: - Display the room shield in all room setting screens Bugfix 🐛: - - + - Fix bad theme change for the MainActivity Translations 🗣: - 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 143506d4df..e6c5abe20c 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -43,6 +43,7 @@ import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.signout.hard.SignedOutActivity import im.vector.app.features.signout.soft.SoftLogoutActivity +import im.vector.app.features.themes.ActivityOtherThemes import im.vector.app.features.ui.UiStateRepository import kotlinx.parcelize.Parcelize import kotlinx.coroutines.Dispatchers @@ -83,6 +84,8 @@ class MainActivity : VectorBaseActivity(), UnlockedActivity override fun getBinding() = ActivityMainBinding.inflate(layoutInflater) + override fun getOtherThemes() = ActivityOtherThemes.Launcher + private lateinit var args: MainActivityArgs @Inject lateinit var notificationDrawerManager: NotificationDrawerManager diff --git a/vector/src/main/java/im/vector/app/features/themes/ActivityOtherThemes.kt b/vector/src/main/java/im/vector/app/features/themes/ActivityOtherThemes.kt index 847caeab4c..a1065ed10b 100644 --- a/vector/src/main/java/im/vector/app/features/themes/ActivityOtherThemes.kt +++ b/vector/src/main/java/im/vector/app/features/themes/ActivityOtherThemes.kt @@ -31,6 +31,11 @@ sealed class ActivityOtherThemes(@StyleRes val dark: Int, R.style.AppTheme_Black ) + object Launcher : ActivityOtherThemes( + R.style.AppTheme_Launcher, + R.style.AppTheme_Launcher + ) + object AttachmentsPreview : ActivityOtherThemes( R.style.AppTheme_AttachmentsPreview, R.style.AppTheme_AttachmentsPreview From adca3de3b5d982136ee6012bcc2eff7425ee31d5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 19 Mar 2021 13:48:49 +0100 Subject: [PATCH 061/249] Improve message with Emoji only detection (#3017) --- CHANGES.md | 1 + vector/src/main/java/im/vector/app/core/utils/Emoji.kt | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index c3fde19bc4..f0a8c2bfe0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ Features ✨: Improvements 🙌: - Display the room shield in all room setting screens + - Improve message with Emoji only detection (#3017) Bugfix 🐛: - Fix bad theme change for the MainActivity diff --git a/vector/src/main/java/im/vector/app/core/utils/Emoji.kt b/vector/src/main/java/im/vector/app/core/utils/Emoji.kt index b3b9a39f30..b665236137 100644 --- a/vector/src/main/java/im/vector/app/core/utils/Emoji.kt +++ b/vector/src/main/java/im/vector/app/core/utils/Emoji.kt @@ -16,8 +16,10 @@ package im.vector.app.core.utils +import com.vanniktech.emoji.EmojiUtils import java.util.regex.Pattern +/* private val emojisPattern = Pattern.compile("((?:[\uD83C\uDF00-\uD83D\uDDFF]" + "|[\uD83E\uDD00-\uD83E\uDDFF]" + "|[\uD83D\uDE00-\uD83D\uDE4F]" + @@ -41,6 +43,7 @@ private val emojisPattern = Pattern.compile("((?:[\uD83C\uDF00-\uD83D\uDDFF]" + "|\uD83C\uDC04\uFE0F?" + "|\uD83C\uDCCF\uFE0F?" + "|[\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA]\uFE0F?))") + */ /* // A hashset from all supported emoji @@ -82,6 +85,9 @@ fun isSingleEmoji(string: String): Boolean { * @return true if the body contains only emojis */ fun containsOnlyEmojis(str: String?): Boolean { + // Now rely on vanniktech library + return EmojiUtils.isOnlyEmojis(str) + /* var res = false if (str != null && str.isNotEmpty()) { @@ -112,6 +118,7 @@ fun containsOnlyEmojis(str: String?): Boolean { } return res + */ } /** From 0eea257a25a089131daa07c9a432f1ed44727cb6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 19 Mar 2021 14:15:17 +0100 Subject: [PATCH 062/249] Improve layout for more clarity --- .../fragment_ssss_access_from_passphrase.xml | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/vector/src/main/res/layout/fragment_ssss_access_from_passphrase.xml b/vector/src/main/res/layout/fragment_ssss_access_from_passphrase.xml index 6dd94d0d75..fc57d53ab3 100644 --- a/vector/src/main/res/layout/fragment_ssss_access_from_passphrase.xml +++ b/vector/src/main/res/layout/fragment_ssss_access_from_passphrase.xml @@ -8,7 +8,9 @@ + android:layout_height="wrap_content" + android:paddingTop="32dp" + android:paddingBottom="32dp"> - - - - - - - - - + + + + + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/ssss_passphrase_or" /> - + \ No newline at end of file From fa1de6e6b0b8baa9a63a6a47bedab2a0f4c03a00 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 19 Mar 2021 14:25:52 +0100 Subject: [PATCH 063/249] Disable color because there is no action behind. --- .../crypto/quads/SharedSecuredStoragePassphraseFragment.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt index 37d8bf9b46..ba2c923d8b 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecuredStoragePassphraseFragment.kt @@ -30,10 +30,8 @@ import im.vector.app.R import im.vector.app.core.extensions.showPassword import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.ColorProvider -import im.vector.app.core.utils.colorizeMatchingText import im.vector.app.databinding.FragmentSsssAccessFromPassphraseBinding import io.reactivex.android.schedulers.AndroidSchedulers - import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -59,8 +57,9 @@ class SharedSecuredStoragePassphraseFragment @Inject constructor( key ) .toSpannable() - .colorizeMatchingText(pass, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) - .colorizeMatchingText(key, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) + // TODO Restore coloration when we will have a FAQ to open with those terms + // .colorizeMatchingText(pass, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) + // .colorizeMatchingText(key, colorProvider.getColorFromAttribute(android.R.attr.textColorLink)) views.ssssPassphraseEnterEdittext.editorActionEvents() .throttleFirst(300, TimeUnit.MILLISECONDS) From 485c44454b49760e66ce4e2c9a10299262cf3b89 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 19 Mar 2021 15:27:56 +0100 Subject: [PATCH 064/249] Handle encrypted reactions (#2509) --- CHANGES.md | 1 + .../session/room/EventRelationsAggregationProcessor.kt | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index f0a8c2bfe0..23252ac709 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ Improvements 🙌: Bugfix 🐛: - Fix bad theme change for the MainActivity + - Handle encrypted reactions (#2509) Translations 🗣: - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index 60440c6359..13aab36911 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -158,6 +158,13 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr } } } + } else if (encryptedEventContent?.relatesTo?.type == RelationType.ANNOTATION) { + // Reaction + if (event.getClearType() == EventType.REACTION) { + // we got a reaction!! + Timber.v("###REACTION e2e in room $roomId , reaction eventID ${event.eventId}") + handleReaction(event, roomId, realm, userId, isLocalEcho) + } } } EventType.REDACTION -> { From 294df236c560bfbb9fe0f0baa866aca8bf6b6fe3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 19 Mar 2021 15:33:25 +0100 Subject: [PATCH 065/249] Hide encrypted reactions from the timeline (#2509) --- .../home/room/detail/timeline/factory/TimelineItemFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index ccc8289e08..73f101d1f5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -64,7 +64,6 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.STATE_ROOM_SERVER_ACL, EventType.STATE_ROOM_GUEST_ACCESS, EventType.STATE_ROOM_POWER_LEVELS, - EventType.REACTION, EventType.REDACTION -> noticeItemFactory.create(event, highlight, callback) EventType.STATE_ROOM_WIDGET_LEGACY, EventType.STATE_ROOM_WIDGET -> widgetItemFactory.create(event, highlight, callback) @@ -91,6 +90,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.KEY_VERIFICATION_KEY, EventType.KEY_VERIFICATION_READY, EventType.KEY_VERIFICATION_MAC, + EventType.REACTION, EventType.CALL_CANDIDATES, EventType.CALL_REPLACES, EventType.CALL_SELECT_ANSWER, From 7e2a5e55f77764bf67ce5823ab9bde44e5d77baf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 19 Mar 2021 15:38:14 +0100 Subject: [PATCH 066/249] Simplify signature, userId is a member of the class --- .../EventRelationsAggregationProcessor.kt | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index 13aab36911..8913842074 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -53,8 +53,9 @@ import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import timber.log.Timber import javax.inject.Inject -internal class EventRelationsAggregationProcessor @Inject constructor(@UserId private val userId: String) - : EventInsertLiveProcessor { +internal class EventRelationsAggregationProcessor @Inject constructor( + @UserId private val userId: String +) : EventInsertLiveProcessor { private val allowedTypes = listOf( EventType.MESSAGE, @@ -87,7 +88,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr EventType.REACTION -> { // we got a reaction!! Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}") - handleReaction(event, roomId, realm, userId, isLocalEcho) + handleReaction(event, roomId, realm, isLocalEcho) } EventType.MESSAGE -> { if (event.unsignedData?.relations?.annotations != null) { @@ -108,7 +109,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr handleReplace(realm, event, content, roomId, isLocalEcho) } else if (content?.relatesTo?.type == RelationType.RESPONSE) { Timber.v("###RESPONSE in room $roomId for event ${event.eventId}") - handleResponse(realm, userId, event, content, roomId, isLocalEcho) + handleResponse(realm, event, content, roomId, isLocalEcho) } } @@ -122,7 +123,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr Timber.v("## SAS REF in room $roomId for event ${event.eventId}") event.content.toModel()?.relatesTo?.let { if (it.type == RelationType.REFERENCE && it.eventId != null) { - handleVerification(realm, event, roomId, isLocalEcho, it.eventId, userId) + handleVerification(realm, event, roomId, isLocalEcho, it.eventId) } } } @@ -140,7 +141,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) } else if (encryptedEventContent.relatesTo.type == RelationType.RESPONSE) { Timber.v("###RESPONSE in room $roomId for event ${event.eventId}") - handleResponse(realm, userId, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) + handleResponse(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) } } } else if (encryptedEventContent?.relatesTo?.type == RelationType.REFERENCE) { @@ -154,7 +155,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr EventType.KEY_VERIFICATION_KEY -> { Timber.v("## SAS REF in room $roomId for event ${event.eventId}") encryptedEventContent.relatesTo.eventId?.let { - handleVerification(realm, event, roomId, isLocalEcho, it, userId) + handleVerification(realm, event, roomId, isLocalEcho, it) } } } @@ -163,7 +164,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr if (event.getClearType() == EventType.REACTION) { // we got a reaction!! Timber.v("###REACTION e2e in room $roomId , reaction eventID ${event.eventId}") - handleReaction(event, roomId, realm, userId, isLocalEcho) + handleReaction(event, roomId, realm, isLocalEcho) } } } @@ -183,7 +184,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr } } EventType.REACTION -> { - handleReactionRedact(eventToPrune, realm, userId) + handleReactionRedact(eventToPrune, realm) } } } @@ -274,7 +275,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr } private fun handleResponse(realm: Realm, - userId: String, event: Event, content: MessageContent, roomId: String, @@ -383,7 +383,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr } } - private fun handleReaction(event: Event, roomId: String, realm: Realm, userId: String, isLocalEcho: Boolean) { + private fun handleReaction(event: Event, roomId: String, realm: Realm, isLocalEcho: Boolean) { val content = event.content.toModel() if (content == null) { Timber.e("Malformed reaction content ${event.content}") @@ -464,7 +464,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr sourceToDiscard.deleteFromRealm() } - private fun handleReactionRedact(eventToPrune: EventEntity, realm: Realm, userId: String) { + private fun handleReactionRedact(eventToPrune: EventEntity, realm: Realm) { Timber.v("REDACTION of reaction ${eventToPrune.eventId}") // delete a reaction, need to update the annotation summary if any val reactionContent: ReactionContent = EventMapper.map(eventToPrune).content.toModel() ?: return @@ -501,7 +501,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr } } - private fun handleVerification(realm: Realm, event: Event, roomId: String, isLocalEcho: Boolean, relatedEventId: String, userId: String) { + private fun handleVerification(realm: Realm, event: Event, roomId: String, isLocalEcho: Boolean, relatedEventId: String) { val eventSummary = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, relatedEventId) val verifSummary = eventSummary.referencesSummaryEntity From c43479420aa43201b79b7f8f0741aee2c94e0302 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 19 Mar 2021 15:42:42 +0100 Subject: [PATCH 067/249] Reorder signature for clarity --- .../EventRelationsAggregationProcessor.kt | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index 8913842074..c7e09e5954 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -88,12 +88,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor( EventType.REACTION -> { // we got a reaction!! Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}") - handleReaction(event, roomId, realm, isLocalEcho) + handleReaction(realm, event, roomId, isLocalEcho) } EventType.MESSAGE -> { if (event.unsignedData?.relations?.annotations != null) { - Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}") - handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm) + Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}") + handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations) EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId ?: "").findFirst() ?.let { @@ -164,7 +164,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( if (event.getClearType() == EventType.REACTION) { // we got a reaction!! Timber.v("###REACTION e2e in room $roomId , reaction eventID ${event.eventId}") - handleReaction(event, roomId, realm, isLocalEcho) + handleReaction(realm, event, roomId, isLocalEcho) } } } @@ -180,11 +180,11 @@ internal class EventRelationsAggregationProcessor @Inject constructor( // was this event a m.replace val contentModel = ContentMapper.map(eventToPrune.content)?.toModel() if (RelationType.REPLACE == contentModel?.relatesTo?.type && contentModel.relatesTo?.eventId != null) { - handleRedactionOfReplace(eventToPrune, contentModel.relatesTo!!.eventId!!, realm) + handleRedactionOfReplace(realm, eventToPrune, contentModel.relatesTo!!.eventId!!) } } EventType.REACTION -> { - handleReactionRedact(eventToPrune, realm) + handleReactionRedact(realm, eventToPrune) } } } @@ -361,7 +361,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor( existingPollSummary.aggregatedContent = ContentMapper.map(sumModel.toContent()) } - private fun handleInitialAggregatedRelations(event: Event, roomId: String, aggregation: AggregatedAnnotation, realm: Realm) { + private fun handleInitialAggregatedRelations(realm: Realm, + event: Event, + roomId: String, + aggregation: AggregatedAnnotation) { if (SHOULD_HANDLE_SERVER_AGREGGATION) { aggregation.chunk?.forEach { if (it.type == EventType.REACTION) { @@ -383,7 +386,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } - private fun handleReaction(event: Event, roomId: String, realm: Realm, isLocalEcho: Boolean) { + private fun handleReaction(realm: Realm, + event: Event, + roomId: String, + isLocalEcho: Boolean) { val content = event.content.toModel() if (content == null) { Timber.e("Malformed reaction content ${event.content}") @@ -448,7 +454,9 @@ internal class EventRelationsAggregationProcessor @Inject constructor( /** * Called when an event is deleted */ - private fun handleRedactionOfReplace(redacted: EventEntity, relatedEventId: String, realm: Realm) { + private fun handleRedactionOfReplace(realm: Realm, + redacted: EventEntity, + relatedEventId: String) { Timber.d("Handle redaction of m.replace") val eventSummary = EventAnnotationsSummaryEntity.where(realm, redacted.roomId, relatedEventId).findFirst() if (eventSummary == null) { @@ -464,7 +472,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor( sourceToDiscard.deleteFromRealm() } - private fun handleReactionRedact(eventToPrune: EventEntity, realm: Realm) { + private fun handleReactionRedact(realm: Realm, + eventToPrune: EventEntity) { Timber.v("REDACTION of reaction ${eventToPrune.eventId}") // delete a reaction, need to update the annotation summary if any val reactionContent: ReactionContent = EventMapper.map(eventToPrune).content.toModel() ?: return From 2ffcc63de882e5759abc98932bb9b63f42183b83 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 19 Mar 2021 16:00:49 +0100 Subject: [PATCH 068/249] Typo --- docs/notifications.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/notifications.md b/docs/notifications.md index 63bf593d0d..a00fef8fae 100644 --- a/docs/notifications.md +++ b/docs/notifications.md @@ -2,7 +2,7 @@ This document aims to describe how Element android displays notifications to the # Table of Contents 1. [Prerequisites Knowledge](#prerequisites-knowledge) - * [How does a matrix client gets a message from a Home Server?](#how-does-a-matrix-client-gets-a-message-from-a-home-server) + * [How does a matrix client get a message from a Home Server?](#how-does-a-matrix-client-get-a-message-from-a-home-server) * [How does a mobile app receives push notification?](#how-does-a-mobile-app-receives-push-notification) * [Push VS Notification](#push-vs-notification) * [Push in the matrix federated world](#push-in-the-matrix-federated-world) @@ -22,7 +22,7 @@ First let's start with some prerequisite knowledge # Prerequisites Knowledge -## How does a matrix client gets a message from a Home Server? +## How does a matrix client get a message from a Home Server? In order to get messages from a home server, a matrix client need to perform a ``sync`` operation. From 684c0332d578dad0dbcf1e3a5b655e8b661183cb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 19 Mar 2021 16:27:31 +0100 Subject: [PATCH 069/249] Remove commented out code --- .../java/im/vector/app/core/utils/Emoji.kt | 90 ------------------- 1 file changed, 90 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/utils/Emoji.kt b/vector/src/main/java/im/vector/app/core/utils/Emoji.kt index b665236137..66907ded10 100644 --- a/vector/src/main/java/im/vector/app/core/utils/Emoji.kt +++ b/vector/src/main/java/im/vector/app/core/utils/Emoji.kt @@ -17,64 +17,6 @@ package im.vector.app.core.utils import com.vanniktech.emoji.EmojiUtils -import java.util.regex.Pattern - -/* -private val emojisPattern = Pattern.compile("((?:[\uD83C\uDF00-\uD83D\uDDFF]" + - "|[\uD83E\uDD00-\uD83E\uDDFF]" + - "|[\uD83D\uDE00-\uD83D\uDE4F]" + - "|[\uD83D\uDE80-\uD83D\uDEFF]" + - "|[\u2600-\u26FF]\uFE0F?" + - "|[\u2700-\u27BF]\uFE0F?" + - "|\u24C2\uFE0F?" + - "|[\uD83C\uDDE6-\uD83C\uDDFF]{1,2}" + - "|[\uD83C\uDD70\uD83C\uDD71\uD83C\uDD7E\uD83C\uDD7F\uD83C\uDD8E\uD83C\uDD91-\uD83C\uDD9A]\uFE0F?" + - "|[\u0023\u002A\u0030-\u0039]\uFE0F?\u20E3" + - "|[\u2194-\u2199\u21A9-\u21AA]\uFE0F?" + - "|[\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55]\uFE0F?" + - "|[\u2934\u2935]\uFE0F?" + - "|[\u3030\u303D]\uFE0F?" + - "|[\u3297\u3299]\uFE0F?" + - "|[\uD83C\uDE01\uD83C\uDE02\uD83C\uDE1A\uD83C\uDE2F\uD83C\uDE32-\uD83C\uDE3A\uD83C\uDE50\uD83C\uDE51]\uFE0F?" + - "|[\u203C\u2049]\uFE0F?" + - "|[\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE]\uFE0F?" + - "|[\u00A9\u00AE]\uFE0F?" + - "|[\u2122\u2139]\uFE0F?" + - "|\uD83C\uDC04\uFE0F?" + - "|\uD83C\uDCCF\uFE0F?" + - "|[\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA]\uFE0F?))") - */ - -/* -// A hashset from all supported emoji -private var knownEmojiSet: HashSet? = null - -fun initKnownEmojiHashSet(context: Context, done: (() -> Unit)? = null) { - GlobalScope.launch { - context.resources.openRawResource(R.raw.emoji_picker_datasource).use { input -> - val moshi = Moshi.Builder().build() - val jsonAdapter = moshi.adapter(EmojiData::class.java) - val inputAsString = input.bufferedReader().use { it.readText() } - val source = jsonAdapter.fromJson(inputAsString) - knownEmojiSet = HashSet().also { - source?.emojis?.mapTo(it) { (_, value) -> - value.emojiString() - } - } - done?.invoke() - } - } -} - -fun isSingleEmoji(string: String): Boolean { - if (knownEmojiSet == null) { - Timber.e("Known Emoji Hashset not initialized") - // use fallback regexp - return containsOnlyEmojis(string) - } - return knownEmojiSet?.contains(string) ?: false -} - */ /** * Test if a string contains emojis. @@ -87,38 +29,6 @@ fun isSingleEmoji(string: String): Boolean { fun containsOnlyEmojis(str: String?): Boolean { // Now rely on vanniktech library return EmojiUtils.isOnlyEmojis(str) - /* - var res = false - - if (str != null && str.isNotEmpty()) { - val matcher = emojisPattern.matcher(str) - - var start = -1 - var end = -1 - - while (matcher.find()) { - val nextStart = matcher.start() - - // first emoji position - if (start < 0) { - if (nextStart > 0) { - return false - } - } else { - // must not have a character between - if (nextStart != end) { - return false - } - } - start = nextStart - end = matcher.end() - } - - res = -1 != start && end == str.length - } - - return res - */ } /** From fa370708842f102de530c53bc6cab11e7f4075c9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 17 Mar 2021 17:05:32 +0100 Subject: [PATCH 070/249] Disable URL preview for some domains (#2995) --- CHANGES.md | 1 + .../room/detail/timeline/url/PreviewUrlRetriever.kt | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index d2ba5babec..55c7387379 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,7 @@ Improvements 🙌: Bugfix 🐛: - Fix bad theme change for the MainActivity - Handle encrypted reactions (#2509) + - Disable URL preview for some domains (#2995) Translations 🗣: - diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt index df75c0094b..4c018c8a99 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlRetriever.kt @@ -52,7 +52,7 @@ class PreviewUrlRetriever(session: Session, // The event is not known or it has been edited // Keep only the first URL for the moment val url = mediaService.extractUrls(event) - .firstOrNull() + .firstOrNull { canShowUrlPreview(it) } ?.takeIf { it !in blockedUrl } if (url == null) { updateState(eventId, latestEventId, PreviewUrlUiState.NoUrl) @@ -98,6 +98,10 @@ class PreviewUrlRetriever(session: Session, } } + private fun canShowUrlPreview(url: String): Boolean { + return blockedDomains.all { !url.startsWith(it) } + } + fun doNotShowPreviewUrlFor(eventId: String, url: String) { blockedUrl.add(url) @@ -143,5 +147,12 @@ class PreviewUrlRetriever(session: Session, companion object { // One week in millis private const val CACHE_VALIDITY: Long = 7 * 24 * 3_600 * 1_000 + + private val blockedDomains = listOf( + "https://matrix.to", + "https://app.element.io", + "https://staging.element.io", + "https://develop.element.io" + ) } } From 7b8ede03bc9ffde664589ad03a7e596c2b4c42d6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 19 Mar 2021 20:09:53 +0100 Subject: [PATCH 071/249] Picture preview when replying. Also add the image preview in the message detail bottomsheet (#2916) --- CHANGES.md | 1 + .../BottomSheetMessagePreviewItem.kt | 13 ++++ .../home/room/detail/RoomDetailFragment.kt | 13 ++++ .../action/MessageActionsEpoxyController.kt | 7 +++ .../image/ImageContentRendererFactory.kt | 63 +++++++++++++++++++ .../src/main/res/layout/composer_layout.xml | 7 +++ ...composer_layout_constraint_set_compact.xml | 9 +++ ...omposer_layout_constraint_set_expanded.xml | 21 +++++-- .../item_bottom_sheet_message_preview.xml | 16 ++++- 9 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/image/ImageContentRendererFactory.kt diff --git a/CHANGES.md b/CHANGES.md index d2ba5babec..9bfc5a59a4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ Improvements 🙌: - Crypto improvement | Bulk send NO_OLM withheld code - Display the room shield in all room setting screens - Improve message with Emoji only detection (#3017) + - Picture preview when replying. Also add the image preview in the message detail bottomsheet (#2916) Bugfix 🐛: - Fix bad theme change for the MainActivity diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt index a323ce995b..2f7c22e4b4 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt @@ -19,6 +19,7 @@ package im.vector.app.core.epoxy.bottomsheet import android.text.method.MovementMethod import android.widget.ImageView import android.widget.TextView +import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R @@ -27,6 +28,7 @@ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.extensions.setTextOrHide import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.tools.findPillsAndProcess +import im.vector.app.features.media.ImageContentRenderer import org.matrix.android.sdk.api.util.MatrixItem /** @@ -44,6 +46,12 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel(R.id.bottom_sheet_message_preview_sender) val body by bind(R.id.bottom_sheet_message_preview_body) val timestamp by bind(R.id.bottom_sheet_message_preview_timestamp) + val imagePreview by bind(R.id.bottom_sheet_message_preview_image) } } 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 a80aeb65b0..64bffcb49c 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 @@ -98,6 +98,7 @@ import im.vector.app.core.ui.views.FailedMessagesWarningView import im.vector.app.core.ui.views.JumpToReadMarkerView import im.vector.app.core.ui.views.NotificationAreaView import im.vector.app.core.utils.Debouncer +import im.vector.app.core.utils.DimensionConverter import im.vector.app.core.utils.KeyboardStateUtils import im.vector.app.core.utils.PERMISSIONS_FOR_WRITING_FILES import im.vector.app.core.utils.TextUtils @@ -137,6 +138,7 @@ import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBot import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider +import im.vector.app.features.home.room.detail.timeline.image.buildImageContentRendererData import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem import im.vector.app.features.home.room.detail.timeline.item.MessageFileItem import im.vector.app.features.home.room.detail.timeline.item.MessageImageVideoItem @@ -223,6 +225,7 @@ class RoomDetailFragment @Inject constructor( private val eventHtmlRenderer: EventHtmlRenderer, private val vectorPreferences: VectorPreferences, private val colorProvider: ColorProvider, + private val dimensionConverter: DimensionConverter, private val notificationUtils: NotificationUtils, private val matrixItemColorProvider: MatrixItemColorProvider, private val imageContentRenderer: ImageContentRenderer, @@ -873,6 +876,15 @@ class RoomDetailFragment @Inject constructor( } views.composerLayout.views.composerRelatedMessageContent.text = (formattedBody ?: nonFormattedBody) + // Image Event + val data = event.buildImageContentRendererData(dimensionConverter.dpToPx(66)) + val isImageVisible = if (data != null) { + imageContentRenderer.render(data, ImageContentRenderer.Mode.THUMBNAIL, views.composerLayout.views.composerRelatedMessageImage) + true + } else { + false + } + updateComposerText(defaultContent) views.composerLayout.views.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes)) @@ -884,6 +896,7 @@ class RoomDetailFragment @Inject constructor( if (isAdded) { // need to do it here also when not using quick reply focusComposerAndShowKeyboard() + views.composerLayout.views.composerRelatedMessageImage.isVisible = isImageVisible } } focusComposerAndShowKeyboard() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt index 4e1492aaba..30587e6659 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt @@ -29,11 +29,14 @@ import im.vector.app.core.epoxy.bottomsheet.bottomSheetQuickReactionsItem import im.vector.app.core.epoxy.bottomsheet.bottomSheetSendStateItem import im.vector.app.core.epoxy.dividerItem import im.vector.app.core.resources.StringProvider +import im.vector.app.core.utils.DimensionConverter import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.home.room.detail.timeline.image.buildImageContentRendererData import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration import im.vector.app.features.home.room.detail.timeline.tools.createLinkMovementMethod import im.vector.app.features.home.room.detail.timeline.tools.linkify +import im.vector.app.features.media.ImageContentRenderer import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.room.send.SendState import javax.inject.Inject @@ -45,6 +48,8 @@ class MessageActionsEpoxyController @Inject constructor( private val stringProvider: StringProvider, private val avatarRenderer: AvatarRenderer, private val fontProvider: EmojiCompatFontProvider, + private val imageContentRenderer: ImageContentRenderer, + private val dimensionConverter: DimensionConverter, private val dateFormatter: VectorDateFormatter ) : TypedEpoxyController() { @@ -59,6 +64,8 @@ class MessageActionsEpoxyController @Inject constructor( avatarRenderer(avatarRenderer) matrixItem(state.informationData.matrixItem) movementMethod(createLinkMovementMethod(listener)) + imageContentRenderer(imageContentRenderer) + data(state.timelineEvent()?.buildImageContentRendererData(dimensionConverter.dpToPx(66))) userClicked { listener?.didSelectMenuAction(EventSharedAction.OpenUserProfile(state.informationData.senderId)) } body(state.messageBody.linkify(listener)) time(formattedDate) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/image/ImageContentRendererFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/image/ImageContentRendererFactory.kt new file mode 100644 index 0000000000..7ff184f664 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/image/ImageContentRendererFactory.kt @@ -0,0 +1,63 @@ +/* + * 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.detail.timeline.image + +import im.vector.app.features.media.ImageContentRenderer +import org.matrix.android.sdk.api.session.events.model.isImageMessage +import org.matrix.android.sdk.api.session.events.model.isVideoMessage +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent +import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent +import org.matrix.android.sdk.api.session.room.model.message.getFileUrl +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt + +fun TimelineEvent.buildImageContentRendererData(maxHeight: Int): ImageContentRenderer.Data? { + return when { + root.isImageMessage() -> root.getClearContent().toModel() + ?.let { messageImageContent -> + ImageContentRenderer.Data( + eventId = eventId, + filename = messageImageContent.body, + mimeType = messageImageContent.mimeType, + url = messageImageContent.getFileUrl(), + elementToDecrypt = messageImageContent.encryptedFileInfo?.toElementToDecrypt(), + height = messageImageContent.info?.height, + maxHeight = maxHeight, + width = messageImageContent.info?.width, + maxWidth = maxHeight * 2, + allowNonMxcUrls = false + ) + } + root.isVideoMessage() -> root.getClearContent().toModel() + ?.let { messageVideoContent -> + ImageContentRenderer.Data( + eventId = eventId, + filename = messageVideoContent.body, + mimeType = messageVideoContent.mimeType, + url = messageVideoContent.getFileUrl(), + elementToDecrypt = messageVideoContent.encryptedFileInfo?.toElementToDecrypt(), + height = messageVideoContent.videoInfo?.height, + maxHeight = maxHeight, + width = messageVideoContent.videoInfo?.width, + maxWidth = maxHeight * 2, + allowNonMxcUrls = false + ) + } + else -> null + } +} diff --git a/vector/src/main/res/layout/composer_layout.xml b/vector/src/main/res/layout/composer_layout.xml index 0db905d015..e837d2d80b 100644 --- a/vector/src/main/res/layout/composer_layout.xml +++ b/vector/src/main/res/layout/composer_layout.xml @@ -68,6 +68,13 @@ app:tint="?riotx_text_primary" tools:ignore="MissingConstraints,MissingPrefix" /> + + + + + - diff --git a/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml b/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml index 5fbed68955..afd96d5690 100644 --- a/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml +++ b/vector/src/main/res/layout/item_bottom_sheet_message_preview.xml @@ -50,6 +50,20 @@ app:layout_constraintEnd_toEndOf="parent" tools:text="Friday 8pm" /> + + From 0fc102461c6b743368ebb4b416c150f84c18140d Mon Sep 17 00:00:00 2001 From: oogm Date: Fri, 19 Mar 2021 22:24:43 +0100 Subject: [PATCH 072/249] Update import_emojis.py to pull keywords from emojilib, update quick reactions --- tools/import_emojis.py | 39 ++++++++++++++++++- .../reactions/data/EmojiDataSource.kt | 12 +++--- .../main/res/raw/emoji_picker_datasource.json | 2 +- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/tools/import_emojis.py b/tools/import_emojis.py index bdbade0e2d..6f204c23f2 100644 --- a/tools/import_emojis.py +++ b/tools/import_emojis.py @@ -1,3 +1,5 @@ +from collections import OrderedDict + import requests import json import re @@ -72,6 +74,41 @@ for row in table.find_all('tr'): "j": emoji_keywords } -# Print result to file (overwrite previous), without escaping unicode characters +# The keywords of unicode.org are usually quite sparse. +# There is no official specification of keywords beyond that, but muan/emojilib maintains a well maintained and +# established repository with additional keywords. We extend our list with the keywords from there. +# At the time of writing it had additional keyword information for all emojis except a few from the newest unicode 13.1. +req = requests.get("https://raw.githubusercontent.com/muan/emojilib/main/dist/emoji-en-US.json") +emojilib_data = json.loads(req.content) + +# We just go over all the official emojis from unicode, and add the keywords there +for emoji in emoji_picker_datasource_emojis: + emoji_name = emoji_picker_datasource_emojis[emoji]["a"] + emoji_code = emoji_picker_datasource_emojis[emoji]["b"] + + # Convert back to actual unicode emoji + emoji_unicode = ''.join(map(lambda s: chr(int(s, 16)), emoji_code.split("-"))) + + # Search for emoji in emojilib + if emoji_unicode in emojilib_data: + emoji_additional_keywords = emojilib_data[emoji_unicode] + elif emoji_unicode+chr(0xfe0f) in emojilib_data: + emoji_additional_keywords = emojilib_data[emoji_unicode+chr(0xfe0f)] + else: + print("No additional keywords for", emoji_unicode, emoji_picker_datasource_emojis[emoji]) + continue + + # 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() + # Remove the ones derived from the unicode name + new_keywords = new_keywords - {emoji.replace("-", "_")} - {emoji.replace("-", " ")} - {emoji_name} + # Write new keywords back + emoji_picker_datasource_emojis[emoji]["j"] = list(new_keywords) + +# 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'] + +# Write result to file (overwrite previous), without escaping unicode characters with open("../vector/src/main/res/raw/emoji_picker_datasource.json", "w") as outfile: json.dump(emoji_picker_datasource, outfile, ensure_ascii=False) diff --git a/vector/src/main/java/im/vector/app/features/reactions/data/EmojiDataSource.kt b/vector/src/main/java/im/vector/app/features/reactions/data/EmojiDataSource.kt index 97f85ea1f5..96eda22eb9 100644 --- a/vector/src/main/java/im/vector/app/features/reactions/data/EmojiDataSource.kt +++ b/vector/src/main/java/im/vector/app/features/reactions/data/EmojiDataSource.kt @@ -100,12 +100,12 @@ class EmojiDataSource @Inject constructor( fun getQuickReactions(): List { if (quickReactions.isEmpty()) { listOf( - "+1", // 👍 - "-1", // 👎 - "grinning", // 😄 - "tada", // 🎉 - "confused", // 😕 - "heart", // ❤️ + "thumbs-up", // 👍 + "thumbs-down", // 👎 + "grinning-face-with-smiling-eyes", // 😄 + "party-popper", // 🎉 + "confused-face", // 😕 + "red-heart", // ❤️ "rocket", // 🚀 "eyes" // 👀 ) diff --git a/vector/src/main/res/raw/emoji_picker_datasource.json b/vector/src/main/res/raw/emoji_picker_datasource.json index bf33808f2f..e0314e9a77 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": ["face", "grin", "grinning face"]}, "grinning-face-with-big-eyes": {"a": "grinning face with big eyes", "b": "1F603", "j": ["face", "grinning face with big eyes", "mouth", "open", "smile"]}, "grinning-face-with-smiling-eyes": {"a": "grinning face with smiling eyes", "b": "1F604", "j": ["eye", "face", "grinning face with smiling eyes", "mouth", "open", "smile"]}, "beaming-face-with-smiling-eyes": {"a": "beaming face with smiling eyes", "b": "1F601", "j": ["beaming face with smiling eyes", "eye", "face", "grin", "smile"]}, "grinning-squinting-face": {"a": "grinning squinting face", "b": "1F606", "j": ["face", "grinning squinting face", "laugh", "mouth", "satisfied", "smile"]}, "grinning-face-with-sweat": {"a": "grinning face with sweat", "b": "1F605", "j": ["cold", "face", "grinning face with sweat", "open", "smile", "sweat"]}, "rolling-on-the-floor-laughing": {"a": "rolling on the floor laughing", "b": "1F923", "j": ["face", "floor", "laugh", "rofl", "rolling", "rolling on the floor laughing", "rotfl"]}, "face-with-tears-of-joy": {"a": "face with tears of joy", "b": "1F602", "j": ["face", "face with tears of joy", "joy", "laugh", "tear"]}, "slightly-smiling-face": {"a": "slightly smiling face", "b": "1F642", "j": ["face", "slightly smiling face", "smile"]}, "upsidedown-face": {"a": "upside-down face", "b": "1F643", "j": ["face", "upside-down"]}, "winking-face": {"a": "winking face", "b": "1F609", "j": ["face", "wink", "winking face"]}, "smiling-face-with-smiling-eyes": {"a": "smiling face with smiling eyes", "b": "1F60A", "j": ["blush", "eye", "face", "smile", "smiling face with smiling eyes"]}, "smiling-face-with-halo": {"a": "smiling face with halo", "b": "1F607", "j": ["angel", "face", "fantasy", "halo", "innocent", "smiling face with halo"]}, "smiling-face-with-hearts": {"a": "smiling face with hearts", "b": "1F970", "j": ["adore", "crush", "hearts", "in love", "smiling face with hearts"]}, "smiling-face-with-hearteyes": {"a": "smiling face with heart-eyes", "b": "1F60D", "j": ["eye", "face", "love", "smile", "smiling face with heart-eyes"]}, "starstruck": {"a": "star-struck", "b": "1F929", "j": ["eyes", "face", "grinning", "star", "star-struck", "starry-eyed"]}, "face-blowing-a-kiss": {"a": "face blowing a kiss", "b": "1F618", "j": ["face", "face blowing a kiss", "kiss"]}, "kissing-face": {"a": "kissing face", "b": "1F617", "j": ["face", "kiss", "kissing face"]}, "smiling-face": {"a": "smiling face", "b": "263A", "j": ["face", "outlined", "relaxed", "smile", "smiling face"]}, "kissing-face-with-closed-eyes": {"a": "kissing face with closed eyes", "b": "1F61A", "j": ["closed", "eye", "face", "kiss", "kissing face with closed eyes"]}, "kissing-face-with-smiling-eyes": {"a": "kissing face with smiling eyes", "b": "1F619", "j": ["eye", "face", "kiss", "kissing face with smiling eyes", "smile"]}, "smiling-face-with-tear": {"a": "smiling face with tear", "b": "1F972", "j": ["grateful", "proud", "relieved", "smiling", "smiling face with tear", "tear", "touched"]}, "face-savoring-food": {"a": "face savoring food", "b": "1F60B", "j": ["delicious", "face", "face savoring food", "savouring", "smile", "yum"]}, "face-with-tongue": {"a": "face with tongue", "b": "1F61B", "j": ["face", "face with tongue", "tongue"]}, "winking-face-with-tongue": {"a": "winking face with tongue", "b": "1F61C", "j": ["eye", "face", "joke", "tongue", "wink", "winking face with tongue"]}, "zany-face": {"a": "zany face", "b": "1F92A", "j": ["eye", "goofy", "large", "small", "zany face"]}, "squinting-face-with-tongue": {"a": "squinting face with tongue", "b": "1F61D", "j": ["eye", "face", "horrible", "squinting face with tongue", "taste", "tongue"]}, "moneymouth-face": {"a": "money-mouth face", "b": "1F911", "j": ["face", "money", "money-mouth face", "mouth"]}, "hugging-face": {"a": "hugging face", "b": "1F917", "j": ["face", "hug", "hugging"]}, "face-with-hand-over-mouth": {"a": "face with hand over mouth", "b": "1F92D", "j": ["face with hand over mouth", "whoops", "shock", "sudden realization", "surprise"]}, "shushing-face": {"a": "shushing face", "b": "1F92B", "j": ["quiet", "shush", "shushing face"]}, "thinking-face": {"a": "thinking face", "b": "1F914", "j": ["face", "thinking"]}, "zippermouth-face": {"a": "zipper-mouth face", "b": "1F910", "j": ["face", "mouth", "zipper", "zipper-mouth face"]}, "face-with-raised-eyebrow": {"a": "face with raised eyebrow", "b": "1F928", "j": ["distrust", "face with raised eyebrow", "skeptic", "disapproval", "disbelief", "mild surprise", "scepticism"]}, "neutral-face": {"a": "neutral face", "b": "1F610", "j": ["deadpan", "face", "meh", "neutral"]}, "expressionless-face": {"a": "expressionless face", "b": "1F611", "j": ["expressionless", "face", "inexpressive", "meh", "unexpressive"]}, "face-without-mouth": {"a": "face without mouth", "b": "1F636", "j": ["face", "face without mouth", "mouth", "quiet", "silent"]}, "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", "smirking face"]}, "unamused-face": {"a": "unamused face", "b": "1F612", "j": ["face", "unamused", "unhappy"]}, "face-with-rolling-eyes": {"a": "face with rolling eyes", "b": "1F644", "j": ["eyeroll", "eyes", "face", "face with rolling eyes", "rolling"]}, "grimacing-face": {"a": "grimacing face", "b": "1F62C", "j": ["face", "grimace", "grimacing 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": ["face", "lie", "lying face", "pinocchio"]}, "relieved-face": {"a": "relieved face", "b": "1F60C", "j": ["face", "relieved"]}, "pensive-face": {"a": "pensive face", "b": "1F614", "j": ["dejected", "face", "pensive"]}, "sleepy-face": {"a": "sleepy face", "b": "1F62A", "j": ["face", "sleep", "sleepy face"]}, "drooling-face": {"a": "drooling face", "b": "1F924", "j": ["drooling", "face"]}, "sleeping-face": {"a": "sleeping face", "b": "1F634", "j": ["face", "sleep", "sleeping face", "zzz"]}, "face-with-medical-mask": {"a": "face with medical mask", "b": "1F637", "j": ["cold", "doctor", "face", "face with medical mask", "mask", "sick"]}, "face-with-thermometer": {"a": "face with thermometer", "b": "1F912", "j": ["face", "face with thermometer", "ill", "sick", "thermometer"]}, "face-with-headbandage": {"a": "face with head-bandage", "b": "1F915", "j": ["bandage", "face", "face with head-bandage", "hurt", "injury"]}, "nauseated-face": {"a": "nauseated face", "b": "1F922", "j": ["face", "nauseated", "vomit"]}, "face-vomiting": {"a": "face vomiting", "b": "1F92E", "j": ["face vomiting", "puke", "sick", "vomit"]}, "sneezing-face": {"a": "sneezing face", "b": "1F927", "j": ["face", "gesundheit", "sneeze", "sneezing face"]}, "hot-face": {"a": "hot face", "b": "1F975", "j": ["feverish", "heat stroke", "hot", "hot face", "red-faced", "sweating"]}, "cold-face": {"a": "cold face", "b": "1F976", "j": ["blue-faced", "cold", "cold face", "freezing", "frostbite", "icicles"]}, "woozy-face": {"a": "woozy face", "b": "1F974", "j": ["dizzy", "intoxicated", "tipsy", "uneven eyes", "wavy mouth", "woozy face"]}, "knockedout-face": {"a": "knocked-out face", "b": "1F635", "j": ["dead", "face", "knocked out", "knocked-out face"]}, "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": ["exploding head", "mind blown", "shocked"]}, "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", "partying face"]}, "disguised-face": {"a": "disguised face", "b": "1F978", "j": ["disguise", "disguised face", "face", "glasses", "incognito", "nose"]}, "smiling-face-with-sunglasses": {"a": "smiling face with sunglasses", "b": "1F60E", "j": ["bright", "cool", "face", "smiling face with sunglasses", "sun", "sunglasses"]}, "nerd-face": {"a": "nerd face", "b": "1F913", "j": ["face", "geek", "nerd"]}, "face-with-monocle": {"a": "face with monocle", "b": "1F9D0", "j": ["face with monocle", "stuffy", "wealthy"]}, "confused-face": {"a": "confused face", "b": "1F615", "j": ["confused", "face", "meh"]}, "worried-face": {"a": "worried face", "b": "1F61F", "j": ["face", "worried"]}, "slightly-frowning-face": {"a": "slightly frowning face", "b": "1F641", "j": ["face", "frown", "slightly frowning face"]}, "frowning-face": {"a": "frowning face", "b": "2639", "j": ["face", "frown", "frowning face"]}, "face-with-open-mouth": {"a": "face with open mouth", "b": "1F62E", "j": ["face", "face with open mouth", "mouth", "open", "sympathy"]}, "hushed-face": {"a": "hushed face", "b": "1F62F", "j": ["face", "hushed", "stunned", "surprised"]}, "astonished-face": {"a": "astonished face", "b": "1F632", "j": ["astonished", "face", "shocked", "totally"]}, "flushed-face": {"a": "flushed face", "b": "1F633", "j": ["dazed", "face", "flushed"]}, "pleading-face": {"a": "pleading face", "b": "1F97A", "j": ["begging", "mercy", "pleading face", "puppy eyes"]}, "frowning-face-with-open-mouth": {"a": "frowning face with open mouth", "b": "1F626", "j": ["face", "frown", "frowning face with open mouth", "mouth", "open"]}, "anguished-face": {"a": "anguished face", "b": "1F627", "j": ["anguished", "face"]}, "fearful-face": {"a": "fearful face", "b": "1F628", "j": ["face", "fear", "fearful", "scared"]}, "anxious-face-with-sweat": {"a": "anxious face with sweat", "b": "1F630", "j": ["anxious face with sweat", "blue", "cold", "face", "rushed", "sweat"]}, "sad-but-relieved-face": {"a": "sad but relieved face", "b": "1F625", "j": ["disappointed", "face", "relieved", "sad but relieved face", "whew"]}, "crying-face": {"a": "crying face", "b": "1F622", "j": ["cry", "crying face", "face", "sad", "tear"]}, "loudly-crying-face": {"a": "loudly crying face", "b": "1F62D", "j": ["cry", "face", "loudly crying face", "sad", "sob", "tear"]}, "face-screaming-in-fear": {"a": "face screaming in fear", "b": "1F631", "j": ["face", "face screaming in fear", "fear", "munch", "scared", "scream"]}, "confounded-face": {"a": "confounded face", "b": "1F616", "j": ["confounded", "face"]}, "persevering-face": {"a": "persevering face", "b": "1F623", "j": ["face", "persevere", "persevering face"]}, "disappointed-face": {"a": "disappointed face", "b": "1F61E", "j": ["disappointed", "face"]}, "downcast-face-with-sweat": {"a": "downcast face with sweat", "b": "1F613", "j": ["cold", "downcast face with sweat", "face", "sweat"]}, "weary-face": {"a": "weary face", "b": "1F629", "j": ["face", "tired", "weary"]}, "tired-face": {"a": "tired face", "b": "1F62B", "j": ["face", "tired"]}, "yawning-face": {"a": "yawning face", "b": "1F971", "j": ["bored", "tired", "yawn", "yawning face"]}, "face-with-steam-from-nose": {"a": "face with steam from nose", "b": "1F624", "j": ["face", "face with steam from nose", "triumph", "won"]}, "pouting-face": {"a": "pouting face", "b": "1F621", "j": ["angry", "face", "mad", "pouting", "rage", "red"]}, "angry-face": {"a": "angry face", "b": "1F620", "j": ["anger", "angry", "face", "mad"]}, "face-with-symbols-on-mouth": {"a": "face with symbols on mouth", "b": "1F92C", "j": ["face with symbols on mouth", "swearing", "cursing"]}, "smiling-face-with-horns": {"a": "smiling face with horns", "b": "1F608", "j": ["face", "fairy tale", "fantasy", "horns", "smile", "smiling face with horns"]}, "angry-face-with-horns": {"a": "angry face with horns", "b": "1F47F", "j": ["angry face with horns", "demon", "devil", "face", "fantasy", "imp"]}, "skull": {"a": "skull", "b": "1F480", "j": ["death", "face", "fairy tale", "monster", "skull"]}, "skull-and-crossbones": {"a": "skull and crossbones", "b": "2620", "j": ["crossbones", "death", "face", "monster", "skull", "skull and crossbones"]}, "pile-of-poo": {"a": "pile of poo", "b": "1F4A9", "j": ["dung", "face", "monster", "pile of poo", "poo", "poop"]}, "clown-face": {"a": "clown face", "b": "1F921", "j": ["clown", "face"]}, "ogre": {"a": "ogre", "b": "1F479", "j": ["creature", "face", "fairy tale", "fantasy", "monster", "ogre", "troll"]}, "goblin": {"a": "goblin", "b": "1F47A", "j": ["creature", "face", "fairy tale", "fantasy", "goblin", "monster"]}, "ghost": {"a": "ghost", "b": "1F47B", "j": ["creature", "face", "fairy tale", "fantasy", "ghost", "monster"]}, "alien": {"a": "alien", "b": "1F47D", "j": ["alien", "creature", "extraterrestrial", "face", "fantasy", "ufo"]}, "alien-monster": {"a": "alien monster", "b": "1F47E", "j": ["alien", "creature", "extraterrestrial", "face", "monster", "ufo"]}, "robot": {"a": "robot", "b": "1F916", "j": ["face", "monster", "robot"]}, "grinning-cat": {"a": "grinning cat", "b": "1F63A", "j": ["cat", "face", "grinning", "mouth", "open", "smile"]}, "grinning-cat-with-smiling-eyes": {"a": "grinning cat with smiling eyes", "b": "1F638", "j": ["cat", "eye", "face", "grin", "grinning cat with smiling eyes", "smile"]}, "cat-with-tears-of-joy": {"a": "cat with tears of joy", "b": "1F639", "j": ["cat", "cat with tears of joy", "face", "joy", "tear"]}, "smiling-cat-with-hearteyes": {"a": "smiling cat with heart-eyes", "b": "1F63B", "j": ["cat", "eye", "face", "heart", "love", "smile", "smiling cat with heart-eyes"]}, "cat-with-wry-smile": {"a": "cat with wry smile", "b": "1F63C", "j": ["cat", "cat with wry smile", "face", "ironic", "smile", "wry"]}, "kissing-cat": {"a": "kissing cat", "b": "1F63D", "j": ["cat", "eye", "face", "kiss", "kissing cat"]}, "weary-cat": {"a": "weary cat", "b": "1F640", "j": ["cat", "face", "oh", "surprised", "weary"]}, "crying-cat": {"a": "crying cat", "b": "1F63F", "j": ["cat", "cry", "crying cat", "face", "sad", "tear"]}, "pouting-cat": {"a": "pouting cat", "b": "1F63E", "j": ["cat", "face", "pouting"]}, "seenoevil-monkey": {"a": "see-no-evil monkey", "b": "1F648", "j": ["evil", "face", "forbidden", "monkey", "see", "see-no-evil monkey"]}, "hearnoevil-monkey": {"a": "hear-no-evil monkey", "b": "1F649", "j": ["evil", "face", "forbidden", "hear", "hear-no-evil monkey", "monkey"]}, "speaknoevil-monkey": {"a": "speak-no-evil monkey", "b": "1F64A", "j": ["evil", "face", "forbidden", "monkey", "speak", "speak-no-evil monkey"]}, "kiss-mark": {"a": "kiss mark", "b": "1F48B", "j": ["kiss", "kiss mark", "lips"]}, "love-letter": {"a": "love letter", "b": "1F48C", "j": ["heart", "letter", "love", "mail"]}, "heart-with-arrow": {"a": "heart with arrow", "b": "1F498", "j": ["arrow", "cupid", "heart with arrow"]}, "heart-with-ribbon": {"a": "heart with ribbon", "b": "1F49D", "j": ["heart with ribbon", "ribbon", "valentine"]}, "sparkling-heart": {"a": "sparkling heart", "b": "1F496", "j": ["excited", "sparkle", "sparkling heart"]}, "growing-heart": {"a": "growing heart", "b": "1F497", "j": ["excited", "growing", "growing heart", "nervous", "pulse"]}, "beating-heart": {"a": "beating heart", "b": "1F493", "j": ["beating", "beating heart", "heartbeat", "pulsating"]}, "revolving-hearts": {"a": "revolving hearts", "b": "1F49E", "j": ["revolving", "revolving hearts"]}, "two-hearts": {"a": "two hearts", "b": "1F495", "j": ["love", "two hearts"]}, "heart-decoration": {"a": "heart decoration", "b": "1F49F", "j": ["heart", "heart decoration"]}, "heart-exclamation": {"a": "heart exclamation", "b": "2763", "j": ["exclamation", "heart exclamation", "mark", "punctuation"]}, "broken-heart": {"a": "broken heart", "b": "1F494", "j": ["break", "broken", "broken heart"]}, "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", "red heart"]}, "orange-heart": {"a": "orange heart", "b": "1F9E1", "j": ["orange", "orange heart"]}, "yellow-heart": {"a": "yellow heart", "b": "1F49B", "j": ["yellow", "yellow heart"]}, "green-heart": {"a": "green heart", "b": "1F49A", "j": ["green", "green heart"]}, "blue-heart": {"a": "blue heart", "b": "1F499", "j": ["blue", "blue heart"]}, "purple-heart": {"a": "purple heart", "b": "1F49C", "j": ["purple", "purple heart"]}, "brown-heart": {"a": "brown heart", "b": "1F90E", "j": ["brown", "heart"]}, "black-heart": {"a": "black heart", "b": "1F5A4", "j": ["black", "black heart", "evil", "wicked"]}, "white-heart": {"a": "white heart", "b": "1F90D", "j": ["heart", "white"]}, "hundred-points": {"a": "hundred points", "b": "1F4AF", "j": ["100", "full", "hundred", "hundred points", "score"]}, "anger-symbol": {"a": "anger symbol", "b": "1F4A2", "j": ["anger symbol", "angry", "comic", "mad"]}, "collision": {"a": "collision", "b": "1F4A5", "j": ["boom", "collision", "comic"]}, "dizzy": {"a": "dizzy", "b": "1F4AB", "j": ["comic", "dizzy", "star"]}, "sweat-droplets": {"a": "sweat droplets", "b": "1F4A6", "j": ["comic", "splashing", "sweat", "sweat droplets"]}, "dashing-away": {"a": "dashing away", "b": "1F4A8", "j": ["comic", "dash", "dashing away", "running"]}, "hole": {"a": "hole", "b": "1F573", "j": ["hole"]}, "bomb": {"a": "bomb", "b": "1F4A3", "j": ["bomb", "comic"]}, "speech-balloon": {"a": "speech balloon", "b": "1F4AC", "j": ["balloon", "bubble", "comic", "dialog", "speech"]}, "eye-in-speech-bubble": {"a": "eye in speech bubble", "b": "1F441-FE0F-200D-1F5E8-FE0F", "j": ["eye", "eye in speech bubble", "speech bubble", "witness"]}, "left-speech-bubble": {"a": "left speech bubble", "b": "1F5E8", "j": ["dialog", "left speech bubble", "speech"]}, "right-anger-bubble": {"a": "right anger bubble", "b": "1F5EF", "j": ["angry", "balloon", "bubble", "mad", "right anger bubble"]}, "thought-balloon": {"a": "thought balloon", "b": "1F4AD", "j": ["balloon", "bubble", "comic", "thought"]}, "zzz": {"a": "zzz", "b": "1F4A4", "j": ["comic", "sleep", "zzz"]}, "waving-hand": {"a": "waving hand", "b": "1F44B", "j": ["hand", "wave", "waving"]}, "raised-back-of-hand": {"a": "raised back of hand", "b": "1F91A", "j": ["backhand", "raised", "raised back of hand"]}, "hand-with-fingers-splayed": {"a": "hand with fingers splayed", "b": "1F590", "j": ["finger", "hand", "hand with fingers splayed", "splayed"]}, "raised-hand": {"a": "raised hand", "b": "270B", "j": ["hand", "high 5", "high five", "raised hand"]}, "vulcan-salute": {"a": "vulcan salute", "b": "1F596", "j": ["finger", "hand", "spock", "vulcan", "vulcan salute"]}, "ok-hand": {"a": "OK hand", "b": "1F44C", "j": ["hand", "OK"]}, "pinched-fingers": {"a": "pinched fingers", "b": "1F90C", "j": ["fingers", "hand gesture", "interrogation", "pinched", "sarcastic"]}, "pinching-hand": {"a": "pinching hand", "b": "1F90F", "j": ["pinching hand", "small amount"]}, "victory-hand": {"a": "victory hand", "b": "270C", "j": ["hand", "v", "victory"]}, "crossed-fingers": {"a": "crossed fingers", "b": "1F91E", "j": ["cross", "crossed fingers", "finger", "hand", "luck"]}, "loveyou-gesture": {"a": "love-you gesture", "b": "1F91F", "j": ["hand", "ILY", "love-you gesture"]}, "sign-of-the-horns": {"a": "sign of the horns", "b": "1F918", "j": ["finger", "hand", "horns", "rock-on", "sign of the horns"]}, "call-me-hand": {"a": "call me hand", "b": "1F919", "j": ["call", "call me hand", "hand"]}, "backhand-index-pointing-left": {"a": "backhand index pointing left", "b": "1F448", "j": ["backhand", "backhand index pointing left", "finger", "hand", "index", "point"]}, "backhand-index-pointing-right": {"a": "backhand index pointing right", "b": "1F449", "j": ["backhand", "backhand index pointing right", "finger", "hand", "index", "point"]}, "backhand-index-pointing-up": {"a": "backhand index pointing up", "b": "1F446", "j": ["backhand", "backhand index pointing up", "finger", "hand", "point", "up"]}, "middle-finger": {"a": "middle finger", "b": "1F595", "j": ["finger", "hand", "middle finger"]}, "backhand-index-pointing-down": {"a": "backhand index pointing down", "b": "1F447", "j": ["backhand", "backhand index pointing down", "down", "finger", "hand", "point"]}, "index-pointing-up": {"a": "index pointing up", "b": "261D", "j": ["finger", "hand", "index", "index pointing up", "point", "up"]}, "thumbs-up": {"a": "thumbs up", "b": "1F44D", "j": ["+1", "hand", "thumb", "thumbs up", "up"]}, "thumbs-down": {"a": "thumbs down", "b": "1F44E", "j": ["-1", "down", "hand", "thumb", "thumbs down"]}, "raised-fist": {"a": "raised fist", "b": "270A", "j": ["clenched", "fist", "hand", "punch", "raised fist"]}, "oncoming-fist": {"a": "oncoming fist", "b": "1F44A", "j": ["clenched", "fist", "hand", "oncoming fist", "punch"]}, "leftfacing-fist": {"a": "left-facing fist", "b": "1F91B", "j": ["fist", "left-facing fist", "leftwards"]}, "rightfacing-fist": {"a": "right-facing fist", "b": "1F91C", "j": ["fist", "right-facing fist", "rightwards"]}, "clapping-hands": {"a": "clapping hands", "b": "1F44F", "j": ["clap", "clapping hands", "hand"]}, "raising-hands": {"a": "raising hands", "b": "1F64C", "j": ["celebration", "gesture", "hand", "hooray", "raised", "raising hands"]}, "open-hands": {"a": "open hands", "b": "1F450", "j": ["hand", "open", "open hands"]}, "palms-up-together": {"a": "palms up together", "b": "1F932", "j": ["palms up together", "prayer", "cupped hands"]}, "handshake": {"a": "handshake", "b": "1F91D", "j": ["agreement", "hand", "handshake", "meeting", "shake"]}, "folded-hands": {"a": "folded hands", "b": "1F64F", "j": ["ask", "folded hands", "hand", "high 5", "high five", "please", "pray", "thanks"]}, "writing-hand": {"a": "writing hand", "b": "270D", "j": ["hand", "write", "writing hand"]}, "nail-polish": {"a": "nail polish", "b": "1F485", "j": ["care", "cosmetics", "manicure", "nail", "polish"]}, "selfie": {"a": "selfie", "b": "1F933", "j": ["camera", "phone", "selfie"]}, "flexed-biceps": {"a": "flexed biceps", "b": "1F4AA", "j": ["biceps", "comic", "flex", "flexed biceps", "muscle"]}, "mechanical-arm": {"a": "mechanical arm", "b": "1F9BE", "j": ["accessibility", "mechanical arm", "prosthetic"]}, "mechanical-leg": {"a": "mechanical leg", "b": "1F9BF", "j": ["accessibility", "mechanical leg", "prosthetic"]}, "leg": {"a": "leg", "b": "1F9B5", "j": ["kick", "leg", "limb"]}, "foot": {"a": "foot", "b": "1F9B6", "j": ["foot", "kick", "stomp"]}, "ear": {"a": "ear", "b": "1F442", "j": ["body", "ear"]}, "ear-with-hearing-aid": {"a": "ear with hearing aid", "b": "1F9BB", "j": ["accessibility", "ear with hearing aid", "hard of hearing"]}, "nose": {"a": "nose", "b": "1F443", "j": ["body", "nose"]}, "brain": {"a": "brain", "b": "1F9E0", "j": ["brain", "intelligent"]}, "anatomical-heart": {"a": "anatomical heart", "b": "1FAC0", "j": ["anatomical", "cardiology", "heart", "organ", "pulse"]}, "lungs": {"a": "lungs", "b": "1FAC1", "j": ["breath", "exhalation", "inhalation", "lungs", "organ", "respiration"]}, "tooth": {"a": "tooth", "b": "1F9B7", "j": ["dentist", "tooth"]}, "bone": {"a": "bone", "b": "1F9B4", "j": ["bone", "skeleton"]}, "eyes": {"a": "eyes", "b": "1F440", "j": ["eye", "eyes", "face"]}, "eye": {"a": "eye", "b": "1F441", "j": ["body", "eye"]}, "tongue": {"a": "tongue", "b": "1F445", "j": ["body", "tongue"]}, "mouth": {"a": "mouth", "b": "1F444", "j": ["lips", "mouth"]}, "baby": {"a": "baby", "b": "1F476", "j": ["baby", "young"]}, "child": {"a": "child", "b": "1F9D2", "j": ["child", "gender-neutral", "unspecified gender", "young"]}, "boy": {"a": "boy", "b": "1F466", "j": ["boy", "young"]}, "girl": {"a": "girl", "b": "1F467", "j": ["girl", "Virgo", "young", "zodiac"]}, "person": {"a": "person", "b": "1F9D1", "j": ["adult", "gender-neutral", "person", "unspecified gender"]}, "person-blond-hair": {"a": "person: blond hair", "b": "1F471", "j": ["blond", "blond-haired person", "hair", "person: blond hair"]}, "man": {"a": "man", "b": "1F468", "j": ["adult", "man"]}, "person-beard": {"a": "person: beard", "b": "1F9D4", "j": ["beard", "person", "person: beard", "bewhiskered"]}, "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"]}, "man-curly-hair": {"a": "man: curly hair", "b": "1F468-200D-1F9B1", "j": ["adult", "curly hair", "man"]}, "man-white-hair": {"a": "man: white hair", "b": "1F468-200D-1F9B3", "j": ["adult", "man", "white hair"]}, "man-bald": {"a": "man: bald", "b": "1F468-200D-1F9B2", "j": ["adult", "bald", "man"]}, "woman": {"a": "woman", "b": "1F469", "j": ["adult", "woman"]}, "woman-red-hair": {"a": "woman: red hair", "b": "1F469-200D-1F9B0", "j": ["adult", "red hair", "woman"]}, "person-red-hair": {"a": "person: red hair", "b": "1F9D1-200D-1F9B0", "j": ["adult", "gender-neutral", "person", "red hair", "unspecified gender"]}, "woman-curly-hair": {"a": "woman: curly hair", "b": "1F469-200D-1F9B1", "j": ["adult", "curly hair", "woman"]}, "person-curly-hair": {"a": "person: curly hair", "b": "1F9D1-200D-1F9B1", "j": ["adult", "curly hair", "gender-neutral", "person", "unspecified gender"]}, "woman-white-hair": {"a": "woman: white hair", "b": "1F469-200D-1F9B3", "j": ["adult", "white hair", "woman"]}, "person-white-hair": {"a": "person: white hair", "b": "1F9D1-200D-1F9B3", "j": ["adult", "gender-neutral", "person", "unspecified gender", "white hair"]}, "woman-bald": {"a": "woman: bald", "b": "1F469-200D-1F9B2", "j": ["adult", "bald", "woman"]}, "person-bald": {"a": "person: bald", "b": "1F9D1-200D-1F9B2", "j": ["adult", "bald", "gender-neutral", "person", "unspecified gender"]}, "woman-blond-hair": {"a": "woman: blond hair", "b": "1F471-200D-2640-FE0F", "j": ["blond-haired woman", "blonde", "hair", "woman", "woman: blond hair"]}, "man-blond-hair": {"a": "man: blond hair", "b": "1F471-200D-2642-FE0F", "j": ["blond", "blond-haired man", "hair", "man", "man: blond hair"]}, "older-person": {"a": "older person", "b": "1F9D3", "j": ["adult", "gender-neutral", "old", "older person", "unspecified gender"]}, "old-man": {"a": "old man", "b": "1F474", "j": ["adult", "man", "old"]}, "old-woman": {"a": "old woman", "b": "1F475", "j": ["adult", "old", "woman"]}, "person-frowning": {"a": "person frowning", "b": "1F64D", "j": ["frown", "gesture", "person frowning"]}, "man-frowning": {"a": "man frowning", "b": "1F64D-200D-2642-FE0F", "j": ["frowning", "gesture", "man"]}, "woman-frowning": {"a": "woman frowning", "b": "1F64D-200D-2640-FE0F", "j": ["frowning", "gesture", "woman"]}, "person-pouting": {"a": "person pouting", "b": "1F64E", "j": ["gesture", "person pouting", "pouting"]}, "man-pouting": {"a": "man pouting", "b": "1F64E-200D-2642-FE0F", "j": ["gesture", "man", "pouting"]}, "woman-pouting": {"a": "woman pouting", "b": "1F64E-200D-2640-FE0F", "j": ["gesture", "pouting", "woman"]}, "person-gesturing-no": {"a": "person gesturing NO", "b": "1F645", "j": ["forbidden", "gesture", "hand", "person gesturing NO", "prohibited"]}, "man-gesturing-no": {"a": "man gesturing NO", "b": "1F645-200D-2642-FE0F", "j": ["forbidden", "gesture", "hand", "man", "man gesturing NO", "prohibited"]}, "woman-gesturing-no": {"a": "woman gesturing NO", "b": "1F645-200D-2640-FE0F", "j": ["forbidden", "gesture", "hand", "prohibited", "woman", "woman gesturing NO"]}, "person-gesturing-ok": {"a": "person gesturing OK", "b": "1F646", "j": ["gesture", "hand", "OK", "person gesturing OK"]}, "man-gesturing-ok": {"a": "man gesturing OK", "b": "1F646-200D-2642-FE0F", "j": ["gesture", "hand", "man", "man gesturing OK", "OK"]}, "woman-gesturing-ok": {"a": "woman gesturing OK", "b": "1F646-200D-2640-FE0F", "j": ["gesture", "hand", "OK", "woman", "woman gesturing OK"]}, "person-tipping-hand": {"a": "person tipping hand", "b": "1F481", "j": ["hand", "help", "information", "person tipping hand", "sassy", "tipping"]}, "man-tipping-hand": {"a": "man tipping hand", "b": "1F481-200D-2642-FE0F", "j": ["man", "man tipping hand", "sassy", "tipping hand"]}, "woman-tipping-hand": {"a": "woman tipping hand", "b": "1F481-200D-2640-FE0F", "j": ["sassy", "tipping hand", "woman", "woman tipping hand"]}, "person-raising-hand": {"a": "person raising hand", "b": "1F64B", "j": ["gesture", "hand", "happy", "person raising hand", "raised"]}, "man-raising-hand": {"a": "man raising hand", "b": "1F64B-200D-2642-FE0F", "j": ["gesture", "man", "man raising hand", "raising hand"]}, "woman-raising-hand": {"a": "woman raising hand", "b": "1F64B-200D-2640-FE0F", "j": ["gesture", "raising hand", "woman", "woman raising hand"]}, "deaf-person": {"a": "deaf person", "b": "1F9CF", "j": ["accessibility", "deaf", "deaf person", "ear", "hear"]}, "deaf-man": {"a": "deaf man", "b": "1F9CF-200D-2642-FE0F", "j": ["deaf", "man"]}, "deaf-woman": {"a": "deaf woman", "b": "1F9CF-200D-2640-FE0F", "j": ["deaf", "woman"]}, "person-bowing": {"a": "person bowing", "b": "1F647", "j": ["apology", "bow", "gesture", "person bowing", "sorry"]}, "man-bowing": {"a": "man bowing", "b": "1F647-200D-2642-FE0F", "j": ["apology", "bowing", "favor", "gesture", "man", "sorry"]}, "woman-bowing": {"a": "woman bowing", "b": "1F647-200D-2640-FE0F", "j": ["apology", "bowing", "favor", "gesture", "sorry", "woman"]}, "person-facepalming": {"a": "person facepalming", "b": "1F926", "j": ["disbelief", "exasperation", "face", "palm", "person facepalming"]}, "man-facepalming": {"a": "man facepalming", "b": "1F926-200D-2642-FE0F", "j": ["disbelief", "exasperation", "facepalm", "man", "man facepalming"]}, "woman-facepalming": {"a": "woman facepalming", "b": "1F926-200D-2640-FE0F", "j": ["disbelief", "exasperation", "facepalm", "woman", "woman facepalming"]}, "person-shrugging": {"a": "person shrugging", "b": "1F937", "j": ["doubt", "ignorance", "indifference", "person shrugging", "shrug"]}, "man-shrugging": {"a": "man shrugging", "b": "1F937-200D-2642-FE0F", "j": ["doubt", "ignorance", "indifference", "man", "man shrugging", "shrug"]}, "woman-shrugging": {"a": "woman shrugging", "b": "1F937-200D-2640-FE0F", "j": ["doubt", "ignorance", "indifference", "shrug", "woman", "woman shrugging"]}, "health-worker": {"a": "health worker", "b": "1F9D1-200D-2695-FE0F", "j": ["doctor", "health worker", "healthcare", "nurse", "therapist"]}, "man-health-worker": {"a": "man health worker", "b": "1F468-200D-2695-FE0F", "j": ["doctor", "healthcare", "man", "man health worker", "nurse", "therapist"]}, "woman-health-worker": {"a": "woman health worker", "b": "1F469-200D-2695-FE0F", "j": ["doctor", "healthcare", "nurse", "therapist", "woman", "woman health worker"]}, "student": {"a": "student", "b": "1F9D1-200D-1F393", "j": ["graduate", "student"]}, "man-student": {"a": "man student", "b": "1F468-200D-1F393", "j": ["graduate", "man", "student"]}, "woman-student": {"a": "woman student", "b": "1F469-200D-1F393", "j": ["graduate", "student", "woman"]}, "teacher": {"a": "teacher", "b": "1F9D1-200D-1F3EB", "j": ["instructor", "professor", "teacher"]}, "man-teacher": {"a": "man teacher", "b": "1F468-200D-1F3EB", "j": ["instructor", "man", "professor", "teacher"]}, "woman-teacher": {"a": "woman teacher", "b": "1F469-200D-1F3EB", "j": ["instructor", "professor", "teacher", "woman"]}, "judge": {"a": "judge", "b": "1F9D1-200D-2696-FE0F", "j": ["judge", "justice", "scales"]}, "man-judge": {"a": "man judge", "b": "1F468-200D-2696-FE0F", "j": ["judge", "justice", "man", "scales"]}, "woman-judge": {"a": "woman judge", "b": "1F469-200D-2696-FE0F", "j": ["judge", "justice", "scales", "woman"]}, "farmer": {"a": "farmer", "b": "1F9D1-200D-1F33E", "j": ["farmer", "gardener", "rancher"]}, "man-farmer": {"a": "man farmer", "b": "1F468-200D-1F33E", "j": ["farmer", "gardener", "man", "rancher"]}, "woman-farmer": {"a": "woman farmer", "b": "1F469-200D-1F33E", "j": ["farmer", "gardener", "rancher", "woman"]}, "cook": {"a": "cook", "b": "1F9D1-200D-1F373", "j": ["chef", "cook"]}, "man-cook": {"a": "man cook", "b": "1F468-200D-1F373", "j": ["chef", "cook", "man"]}, "woman-cook": {"a": "woman cook", "b": "1F469-200D-1F373", "j": ["chef", "cook", "woman"]}, "mechanic": {"a": "mechanic", "b": "1F9D1-200D-1F527", "j": ["electrician", "mechanic", "plumber", "tradesperson"]}, "man-mechanic": {"a": "man mechanic", "b": "1F468-200D-1F527", "j": ["electrician", "man", "mechanic", "plumber", "tradesperson"]}, "woman-mechanic": {"a": "woman mechanic", "b": "1F469-200D-1F527", "j": ["electrician", "mechanic", "plumber", "tradesperson", "woman"]}, "factory-worker": {"a": "factory worker", "b": "1F9D1-200D-1F3ED", "j": ["assembly", "factory", "industrial", "worker"]}, "man-factory-worker": {"a": "man factory worker", "b": "1F468-200D-1F3ED", "j": ["assembly", "factory", "industrial", "man", "worker"]}, "woman-factory-worker": {"a": "woman factory worker", "b": "1F469-200D-1F3ED", "j": ["assembly", "factory", "industrial", "woman", "worker"]}, "office-worker": {"a": "office worker", "b": "1F9D1-200D-1F4BC", "j": ["architect", "business", "manager", "office worker", "white-collar"]}, "man-office-worker": {"a": "man office worker", "b": "1F468-200D-1F4BC", "j": ["architect", "business", "man", "man office worker", "manager", "white-collar"]}, "woman-office-worker": {"a": "woman office worker", "b": "1F469-200D-1F4BC", "j": ["architect", "business", "manager", "white-collar", "woman", "woman office worker"]}, "scientist": {"a": "scientist", "b": "1F9D1-200D-1F52C", "j": ["biologist", "chemist", "engineer", "physicist", "scientist"]}, "man-scientist": {"a": "man scientist", "b": "1F468-200D-1F52C", "j": ["biologist", "chemist", "engineer", "man", "physicist", "scientist"]}, "woman-scientist": {"a": "woman scientist", "b": "1F469-200D-1F52C", "j": ["biologist", "chemist", "engineer", "physicist", "scientist", "woman"]}, "technologist": {"a": "technologist", "b": "1F9D1-200D-1F4BB", "j": ["coder", "developer", "inventor", "software", "technologist"]}, "man-technologist": {"a": "man technologist", "b": "1F468-200D-1F4BB", "j": ["coder", "developer", "inventor", "man", "software", "technologist"]}, "woman-technologist": {"a": "woman technologist", "b": "1F469-200D-1F4BB", "j": ["coder", "developer", "inventor", "software", "technologist", "woman"]}, "singer": {"a": "singer", "b": "1F9D1-200D-1F3A4", "j": ["actor", "entertainer", "rock", "singer", "star"]}, "man-singer": {"a": "man singer", "b": "1F468-200D-1F3A4", "j": ["actor", "entertainer", "man", "rock", "singer", "star"]}, "woman-singer": {"a": "woman singer", "b": "1F469-200D-1F3A4", "j": ["actor", "entertainer", "rock", "singer", "star", "woman"]}, "artist": {"a": "artist", "b": "1F9D1-200D-1F3A8", "j": ["artist", "palette"]}, "man-artist": {"a": "man artist", "b": "1F468-200D-1F3A8", "j": ["artist", "man", "palette"]}, "woman-artist": {"a": "woman artist", "b": "1F469-200D-1F3A8", "j": ["artist", "palette", "woman"]}, "pilot": {"a": "pilot", "b": "1F9D1-200D-2708-FE0F", "j": ["pilot", "plane"]}, "man-pilot": {"a": "man pilot", "b": "1F468-200D-2708-FE0F", "j": ["man", "pilot", "plane"]}, "woman-pilot": {"a": "woman pilot", "b": "1F469-200D-2708-FE0F", "j": ["pilot", "plane", "woman"]}, "astronaut": {"a": "astronaut", "b": "1F9D1-200D-1F680", "j": ["astronaut", "rocket"]}, "man-astronaut": {"a": "man astronaut", "b": "1F468-200D-1F680", "j": ["astronaut", "man", "rocket"]}, "woman-astronaut": {"a": "woman astronaut", "b": "1F469-200D-1F680", "j": ["astronaut", "rocket", "woman"]}, "firefighter": {"a": "firefighter", "b": "1F9D1-200D-1F692", "j": ["firefighter", "firetruck"]}, "man-firefighter": {"a": "man firefighter", "b": "1F468-200D-1F692", "j": ["firefighter", "firetruck", "man"]}, "woman-firefighter": {"a": "woman firefighter", "b": "1F469-200D-1F692", "j": ["firefighter", "firetruck", "woman"]}, "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"]}, "woman-police-officer": {"a": "woman police officer", "b": "1F46E-200D-2640-FE0F", "j": ["cop", "officer", "police", "woman"]}, "detective": {"a": "detective", "b": "1F575", "j": ["detective", "sleuth", "spy"]}, "man-detective": {"a": "man detective", "b": "1F575-FE0F-200D-2642-FE0F", "j": ["detective", "man", "sleuth", "spy"]}, "woman-detective": {"a": "woman detective", "b": "1F575-FE0F-200D-2640-FE0F", "j": ["detective", "sleuth", "spy", "woman"]}, "guard": {"a": "guard", "b": "1F482", "j": ["guard"]}, "man-guard": {"a": "man guard", "b": "1F482-200D-2642-FE0F", "j": ["guard", "man"]}, "woman-guard": {"a": "woman guard", "b": "1F482-200D-2640-FE0F", "j": ["guard", "woman"]}, "ninja": {"a": "ninja", "b": "1F977", "j": ["fighter", "hidden", "ninja", "stealth"]}, "construction-worker": {"a": "construction worker", "b": "1F477", "j": ["construction", "hat", "worker"]}, "man-construction-worker": {"a": "man construction worker", "b": "1F477-200D-2642-FE0F", "j": ["construction", "man", "worker"]}, "woman-construction-worker": {"a": "woman construction worker", "b": "1F477-200D-2640-FE0F", "j": ["construction", "woman", "worker"]}, "prince": {"a": "prince", "b": "1F934", "j": ["prince"]}, "princess": {"a": "princess", "b": "1F478", "j": ["fairy tale", "fantasy", "princess"]}, "person-wearing-turban": {"a": "person wearing turban", "b": "1F473", "j": ["person wearing turban", "turban"]}, "man-wearing-turban": {"a": "man wearing turban", "b": "1F473-200D-2642-FE0F", "j": ["man", "man wearing turban", "turban"]}, "woman-wearing-turban": {"a": "woman wearing turban", "b": "1F473-200D-2640-FE0F", "j": ["turban", "woman", "woman wearing turban"]}, "person-with-skullcap": {"a": "person with skullcap", "b": "1F472", "j": ["cap", "gua pi mao", "hat", "person", "person with skullcap", "skullcap"]}, "woman-with-headscarf": {"a": "woman with headscarf", "b": "1F9D5", "j": ["headscarf", "hijab", "mantilla", "tichel", "woman with headscarf", "bandana", "head kerchief"]}, "person-in-tuxedo": {"a": "person in tuxedo", "b": "1F935", "j": ["groom", "person", "person in tuxedo", "tuxedo"]}, "man-in-tuxedo": {"a": "man in tuxedo", "b": "1F935-200D-2642-FE0F", "j": ["man", "man in tuxedo", "tuxedo"]}, "woman-in-tuxedo": {"a": "woman in tuxedo", "b": "1F935-200D-2640-FE0F", "j": ["tuxedo", "woman", "woman in tuxedo"]}, "person-with-veil": {"a": "person with veil", "b": "1F470", "j": ["bride", "person", "person with veil", "veil", "wedding"]}, "man-with-veil": {"a": "man with veil", "b": "1F470-200D-2642-FE0F", "j": ["man", "man with veil", "veil"]}, "woman-with-veil": {"a": "woman with veil", "b": "1F470-200D-2640-FE0F", "j": ["veil", "woman", "woman with veil"]}, "pregnant-woman": {"a": "pregnant woman", "b": "1F930", "j": ["pregnant", "woman"]}, "breastfeeding": {"a": "breast-feeding", "b": "1F931", "j": ["baby", "breast", "breast-feeding", "nursing"]}, "woman-feeding-baby": {"a": "woman feeding baby", "b": "1F469-200D-1F37C", "j": ["baby", "feeding", "nursing", "woman"]}, "man-feeding-baby": {"a": "man feeding baby", "b": "1F468-200D-1F37C", "j": ["baby", "feeding", "man", "nursing"]}, "person-feeding-baby": {"a": "person feeding baby", "b": "1F9D1-200D-1F37C", "j": ["baby", "feeding", "nursing", "person"]}, "baby-angel": {"a": "baby angel", "b": "1F47C", "j": ["angel", "baby", "face", "fairy tale", "fantasy"]}, "santa-claus": {"a": "Santa Claus", "b": "1F385", "j": ["celebration", "Christmas", "claus", "father", "santa", "Santa Claus"]}, "mrs-claus": {"a": "Mrs. Claus", "b": "1F936", "j": ["celebration", "Christmas", "claus", "mother", "Mrs.", "Mrs. Claus"]}, "mx-claus": {"a": "mx claus", "b": "1F9D1-200D-1F384", "j": ["Claus, christmas", "mx claus"]}, "superhero": {"a": "superhero", "b": "1F9B8", "j": ["good", "hero", "heroine", "superhero", "superpower"]}, "man-superhero": {"a": "man superhero", "b": "1F9B8-200D-2642-FE0F", "j": ["good", "hero", "man", "man superhero", "superpower"]}, "woman-superhero": {"a": "woman superhero", "b": "1F9B8-200D-2640-FE0F", "j": ["good", "hero", "heroine", "superpower", "woman", "woman superhero"]}, "supervillain": {"a": "supervillain", "b": "1F9B9", "j": ["criminal", "evil", "superpower", "supervillain", "villain"]}, "man-supervillain": {"a": "man supervillain", "b": "1F9B9-200D-2642-FE0F", "j": ["criminal", "evil", "man", "man supervillain", "superpower", "villain"]}, "woman-supervillain": {"a": "woman supervillain", "b": "1F9B9-200D-2640-FE0F", "j": ["criminal", "evil", "superpower", "villain", "woman", "woman supervillain"]}, "mage": {"a": "mage", "b": "1F9D9", "j": ["mage", "sorcerer", "sorceress", "witch", "wizard"]}, "man-mage": {"a": "man mage", "b": "1F9D9-200D-2642-FE0F", "j": ["man mage", "sorcerer", "wizard"]}, "woman-mage": {"a": "woman mage", "b": "1F9D9-200D-2640-FE0F", "j": ["sorceress", "witch", "woman mage"]}, "fairy": {"a": "fairy", "b": "1F9DA", "j": ["fairy", "Oberon", "Puck", "Titania"]}, "man-fairy": {"a": "man fairy", "b": "1F9DA-200D-2642-FE0F", "j": ["man fairy", "Oberon", "Puck"]}, "woman-fairy": {"a": "woman fairy", "b": "1F9DA-200D-2640-FE0F", "j": ["Titania", "woman fairy"]}, "vampire": {"a": "vampire", "b": "1F9DB", "j": ["Dracula", "undead", "vampire"]}, "man-vampire": {"a": "man vampire", "b": "1F9DB-200D-2642-FE0F", "j": ["Dracula", "man vampire", "undead"]}, "woman-vampire": {"a": "woman vampire", "b": "1F9DB-200D-2640-FE0F", "j": ["undead", "woman vampire"]}, "merperson": {"a": "merperson", "b": "1F9DC", "j": ["mermaid", "merman", "merperson", "merwoman"]}, "merman": {"a": "merman", "b": "1F9DC-200D-2642-FE0F", "j": ["merman", "Triton"]}, "mermaid": {"a": "mermaid", "b": "1F9DC-200D-2640-FE0F", "j": ["mermaid", "merwoman"]}, "elf": {"a": "elf", "b": "1F9DD", "j": ["elf", "magical", "LOTR style"]}, "man-elf": {"a": "man elf", "b": "1F9DD-200D-2642-FE0F", "j": ["magical", "man elf"]}, "woman-elf": {"a": "woman elf", "b": "1F9DD-200D-2640-FE0F", "j": ["magical", "woman elf"]}, "genie": {"a": "genie", "b": "1F9DE", "j": ["djinn", "genie", "(non-human color)"]}, "man-genie": {"a": "man genie", "b": "1F9DE-200D-2642-FE0F", "j": ["djinn", "man genie"]}, "woman-genie": {"a": "woman genie", "b": "1F9DE-200D-2640-FE0F", "j": ["djinn", "woman genie"]}, "zombie": {"a": "zombie", "b": "1F9DF", "j": ["undead", "walking dead", "zombie", "(non-human color)"]}, "man-zombie": {"a": "man zombie", "b": "1F9DF-200D-2642-FE0F", "j": ["man zombie", "undead", "walking dead"]}, "woman-zombie": {"a": "woman zombie", "b": "1F9DF-200D-2640-FE0F", "j": ["undead", "walking dead", "woman zombie"]}, "person-getting-massage": {"a": "person getting massage", "b": "1F486", "j": ["face", "massage", "person getting massage", "salon"]}, "man-getting-massage": {"a": "man getting massage", "b": "1F486-200D-2642-FE0F", "j": ["face", "man", "man getting massage", "massage"]}, "woman-getting-massage": {"a": "woman getting massage", "b": "1F486-200D-2640-FE0F", "j": ["face", "massage", "woman", "woman getting massage"]}, "person-getting-haircut": {"a": "person getting haircut", "b": "1F487", "j": ["barber", "beauty", "haircut", "parlor", "person getting haircut"]}, "man-getting-haircut": {"a": "man getting haircut", "b": "1F487-200D-2642-FE0F", "j": ["haircut", "man", "man getting haircut"]}, "woman-getting-haircut": {"a": "woman getting haircut", "b": "1F487-200D-2640-FE0F", "j": ["haircut", "woman", "woman getting haircut"]}, "person-walking": {"a": "person walking", "b": "1F6B6", "j": ["hike", "person walking", "walk", "walking"]}, "man-walking": {"a": "man walking", "b": "1F6B6-200D-2642-FE0F", "j": ["hike", "man", "man walking", "walk"]}, "woman-walking": {"a": "woman walking", "b": "1F6B6-200D-2640-FE0F", "j": ["hike", "walk", "woman", "woman walking"]}, "person-standing": {"a": "person standing", "b": "1F9CD", "j": ["person standing", "stand", "standing"]}, "man-standing": {"a": "man standing", "b": "1F9CD-200D-2642-FE0F", "j": ["man", "standing"]}, "woman-standing": {"a": "woman standing", "b": "1F9CD-200D-2640-FE0F", "j": ["standing", "woman"]}, "person-kneeling": {"a": "person kneeling", "b": "1F9CE", "j": ["kneel", "kneeling", "person kneeling"]}, "man-kneeling": {"a": "man kneeling", "b": "1F9CE-200D-2642-FE0F", "j": ["kneeling", "man"]}, "woman-kneeling": {"a": "woman kneeling", "b": "1F9CE-200D-2640-FE0F", "j": ["kneeling", "woman"]}, "person-with-white-cane": {"a": "person with white cane", "b": "1F9D1-200D-1F9AF", "j": ["accessibility", "blind", "person with white cane"]}, "man-with-white-cane": {"a": "man with white cane", "b": "1F468-200D-1F9AF", "j": ["accessibility", "blind", "man", "man with white cane"]}, "woman-with-white-cane": {"a": "woman with white cane", "b": "1F469-200D-1F9AF", "j": ["accessibility", "blind", "woman", "woman with white cane"]}, "person-in-motorized-wheelchair": {"a": "person in motorized wheelchair", "b": "1F9D1-200D-1F9BC", "j": ["accessibility", "person in motorized wheelchair", "wheelchair"]}, "man-in-motorized-wheelchair": {"a": "man in motorized wheelchair", "b": "1F468-200D-1F9BC", "j": ["accessibility", "man", "man in motorized wheelchair", "wheelchair"]}, "woman-in-motorized-wheelchair": {"a": "woman in motorized wheelchair", "b": "1F469-200D-1F9BC", "j": ["accessibility", "wheelchair", "woman", "woman in motorized wheelchair"]}, "person-in-manual-wheelchair": {"a": "person in manual wheelchair", "b": "1F9D1-200D-1F9BD", "j": ["accessibility", "person in manual wheelchair", "wheelchair"]}, "man-in-manual-wheelchair": {"a": "man in manual wheelchair", "b": "1F468-200D-1F9BD", "j": ["accessibility", "man", "man in manual wheelchair", "wheelchair"]}, "woman-in-manual-wheelchair": {"a": "woman in manual wheelchair", "b": "1F469-200D-1F9BD", "j": ["accessibility", "wheelchair", "woman", "woman in manual wheelchair"]}, "person-running": {"a": "person running", "b": "1F3C3", "j": ["marathon", "person running", "running"]}, "man-running": {"a": "man running", "b": "1F3C3-200D-2642-FE0F", "j": ["man", "marathon", "racing", "running"]}, "woman-running": {"a": "woman running", "b": "1F3C3-200D-2640-FE0F", "j": ["marathon", "racing", "running", "woman"]}, "woman-dancing": {"a": "woman dancing", "b": "1F483", "j": ["dance", "dancing", "woman"]}, "man-dancing": {"a": "man dancing", "b": "1F57A", "j": ["dance", "dancing", "man"]}, "person-in-suit-levitating": {"a": "person in suit levitating", "b": "1F574", "j": ["business", "person", "person in suit levitating", "suit"]}, "people-with-bunny-ears": {"a": "people with bunny ears", "b": "1F46F", "j": ["bunny ear", "dancer", "partying", "people with bunny ears"]}, "men-with-bunny-ears": {"a": "men with bunny ears", "b": "1F46F-200D-2642-FE0F", "j": ["bunny ear", "dancer", "men", "men with bunny ears", "partying"]}, "women-with-bunny-ears": {"a": "women with bunny ears", "b": "1F46F-200D-2640-FE0F", "j": ["bunny ear", "dancer", "partying", "women", "women with bunny ears"]}, "person-in-steamy-room": {"a": "person in steamy room", "b": "1F9D6", "j": ["person in steamy room", "sauna", "steam room", "hamam", "steambath"]}, "man-in-steamy-room": {"a": "man in steamy room", "b": "1F9D6-200D-2642-FE0F", "j": ["man in steamy room", "sauna", "steam room"]}, "woman-in-steamy-room": {"a": "woman in steamy room", "b": "1F9D6-200D-2640-FE0F", "j": ["sauna", "steam room", "woman in steamy room"]}, "person-climbing": {"a": "person climbing", "b": "1F9D7", "j": ["climber", "person climbing"]}, "man-climbing": {"a": "man climbing", "b": "1F9D7-200D-2642-FE0F", "j": ["climber", "man climbing"]}, "woman-climbing": {"a": "woman climbing", "b": "1F9D7-200D-2640-FE0F", "j": ["climber", "woman climbing"]}, "person-fencing": {"a": "person fencing", "b": "1F93A", "j": ["fencer", "fencing", "person fencing", "sword"]}, "horse-racing": {"a": "horse racing", "b": "1F3C7", "j": ["horse", "jockey", "racehorse", "racing"]}, "skier": {"a": "skier", "b": "26F7", "j": ["ski", "skier", "snow"]}, "snowboarder": {"a": "snowboarder", "b": "1F3C2", "j": ["ski", "snow", "snowboard", "snowboarder"]}, "person-golfing": {"a": "person golfing", "b": "1F3CC", "j": ["ball", "golf", "person golfing"]}, "man-golfing": {"a": "man golfing", "b": "1F3CC-FE0F-200D-2642-FE0F", "j": ["golf", "man", "man golfing"]}, "woman-golfing": {"a": "woman golfing", "b": "1F3CC-FE0F-200D-2640-FE0F", "j": ["golf", "woman", "woman golfing"]}, "person-surfing": {"a": "person surfing", "b": "1F3C4", "j": ["person surfing", "surfing"]}, "man-surfing": {"a": "man surfing", "b": "1F3C4-200D-2642-FE0F", "j": ["man", "surfing"]}, "woman-surfing": {"a": "woman surfing", "b": "1F3C4-200D-2640-FE0F", "j": ["surfing", "woman"]}, "person-rowing-boat": {"a": "person rowing boat", "b": "1F6A3", "j": ["boat", "person rowing boat", "rowboat"]}, "man-rowing-boat": {"a": "man rowing boat", "b": "1F6A3-200D-2642-FE0F", "j": ["boat", "man", "man rowing boat", "rowboat"]}, "woman-rowing-boat": {"a": "woman rowing boat", "b": "1F6A3-200D-2640-FE0F", "j": ["boat", "rowboat", "woman", "woman rowing boat"]}, "person-swimming": {"a": "person swimming", "b": "1F3CA", "j": ["person swimming", "swim"]}, "man-swimming": {"a": "man swimming", "b": "1F3CA-200D-2642-FE0F", "j": ["man", "man swimming", "swim"]}, "woman-swimming": {"a": "woman swimming", "b": "1F3CA-200D-2640-FE0F", "j": ["swim", "woman", "woman swimming"]}, "person-bouncing-ball": {"a": "person bouncing ball", "b": "26F9", "j": ["ball", "person bouncing ball"]}, "man-bouncing-ball": {"a": "man bouncing ball", "b": "26F9-FE0F-200D-2642-FE0F", "j": ["ball", "man", "man bouncing ball"]}, "woman-bouncing-ball": {"a": "woman bouncing ball", "b": "26F9-FE0F-200D-2640-FE0F", "j": ["ball", "woman", "woman bouncing ball"]}, "person-lifting-weights": {"a": "person lifting weights", "b": "1F3CB", "j": ["lifter", "person lifting weights", "weight"]}, "man-lifting-weights": {"a": "man lifting weights", "b": "1F3CB-FE0F-200D-2642-FE0F", "j": ["man", "man lifting weights", "weight lifter"]}, "woman-lifting-weights": {"a": "woman lifting weights", "b": "1F3CB-FE0F-200D-2640-FE0F", "j": ["weight lifter", "woman", "woman lifting weights"]}, "person-biking": {"a": "person biking", "b": "1F6B4", "j": ["bicycle", "biking", "cyclist", "person biking"]}, "man-biking": {"a": "man biking", "b": "1F6B4-200D-2642-FE0F", "j": ["bicycle", "biking", "cyclist", "man"]}, "woman-biking": {"a": "woman biking", "b": "1F6B4-200D-2640-FE0F", "j": ["bicycle", "biking", "cyclist", "woman"]}, "person-mountain-biking": {"a": "person mountain biking", "b": "1F6B5", "j": ["bicycle", "bicyclist", "bike", "cyclist", "mountain", "person mountain biking"]}, "man-mountain-biking": {"a": "man mountain biking", "b": "1F6B5-200D-2642-FE0F", "j": ["bicycle", "bike", "cyclist", "man", "man mountain biking", "mountain"]}, "woman-mountain-biking": {"a": "woman mountain biking", "b": "1F6B5-200D-2640-FE0F", "j": ["bicycle", "bike", "biking", "cyclist", "mountain", "woman"]}, "person-cartwheeling": {"a": "person cartwheeling", "b": "1F938", "j": ["cartwheel", "gymnastics", "person cartwheeling"]}, "man-cartwheeling": {"a": "man cartwheeling", "b": "1F938-200D-2642-FE0F", "j": ["cartwheel", "gymnastics", "man", "man cartwheeling"]}, "woman-cartwheeling": {"a": "woman cartwheeling", "b": "1F938-200D-2640-FE0F", "j": ["cartwheel", "gymnastics", "woman", "woman cartwheeling"]}, "people-wrestling": {"a": "people wrestling", "b": "1F93C", "j": ["people wrestling", "wrestle", "wrestler"]}, "men-wrestling": {"a": "men wrestling", "b": "1F93C-200D-2642-FE0F", "j": ["men", "men wrestling", "wrestle"]}, "women-wrestling": {"a": "women wrestling", "b": "1F93C-200D-2640-FE0F", "j": ["women", "women wrestling", "wrestle"]}, "person-playing-water-polo": {"a": "person playing water polo", "b": "1F93D", "j": ["person playing water polo", "polo", "water"]}, "man-playing-water-polo": {"a": "man playing water polo", "b": "1F93D-200D-2642-FE0F", "j": ["man", "man playing water polo", "water polo"]}, "woman-playing-water-polo": {"a": "woman playing water polo", "b": "1F93D-200D-2640-FE0F", "j": ["water polo", "woman", "woman playing water polo"]}, "person-playing-handball": {"a": "person playing handball", "b": "1F93E", "j": ["ball", "handball", "person playing handball"]}, "man-playing-handball": {"a": "man playing handball", "b": "1F93E-200D-2642-FE0F", "j": ["handball", "man", "man playing handball"]}, "woman-playing-handball": {"a": "woman playing handball", "b": "1F93E-200D-2640-FE0F", "j": ["handball", "woman", "woman playing handball"]}, "person-juggling": {"a": "person juggling", "b": "1F939", "j": ["balance", "juggle", "multitask", "person juggling", "skill"]}, "man-juggling": {"a": "man juggling", "b": "1F939-200D-2642-FE0F", "j": ["juggling", "man", "multitask"]}, "woman-juggling": {"a": "woman juggling", "b": "1F939-200D-2640-FE0F", "j": ["juggling", "multitask", "woman"]}, "person-in-lotus-position": {"a": "person in lotus position", "b": "1F9D8", "j": ["meditation", "person in lotus position", "yoga", "serenity"]}, "man-in-lotus-position": {"a": "man in lotus position", "b": "1F9D8-200D-2642-FE0F", "j": ["man in lotus position", "meditation", "yoga"]}, "woman-in-lotus-position": {"a": "woman in lotus position", "b": "1F9D8-200D-2640-FE0F", "j": ["meditation", "woman in lotus position", "yoga"]}, "person-taking-bath": {"a": "person taking bath", "b": "1F6C0", "j": ["bath", "bathtub", "person taking bath"]}, "person-in-bed": {"a": "person in bed", "b": "1F6CC", "j": ["hotel", "person in bed", "sleep"]}, "people-holding-hands": {"a": "people holding hands", "b": "1F9D1-200D-1F91D-200D-1F9D1", "j": ["couple", "hand", "hold", "holding hands", "people holding hands", "person"]}, "women-holding-hands": {"a": "women holding hands", "b": "1F46D", "j": ["couple", "hand", "holding hands", "women", "women holding hands"]}, "woman-and-man-holding-hands": {"a": "woman and man holding hands", "b": "1F46B", "j": ["couple", "hand", "hold", "holding hands", "man", "woman", "woman and man holding hands"]}, "men-holding-hands": {"a": "men holding hands", "b": "1F46C", "j": ["couple", "Gemini", "holding hands", "man", "men", "men holding hands", "twins", "zodiac"]}, "kiss": {"a": "kiss", "b": "1F48F", "j": ["couple", "kiss"]}, "kiss-woman-man": {"a": "kiss: woman, man", "b": "1F469-200D-2764-FE0F-200D-1F48B-200D-1F468", "j": ["couple", "kiss", "man", "woman"]}, "kiss-man-man": {"a": "kiss: man, man", "b": "1F468-200D-2764-FE0F-200D-1F48B-200D-1F468", "j": ["couple", "kiss", "man"]}, "kiss-woman-woman": {"a": "kiss: woman, woman", "b": "1F469-200D-2764-FE0F-200D-1F48B-200D-1F469", "j": ["couple", "kiss", "woman"]}, "couple-with-heart": {"a": "couple with heart", "b": "1F491", "j": ["couple", "couple with heart", "love"]}, "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"]}, "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"]}, "family": {"a": "family", "b": "1F46A", "j": ["family"]}, "family-man-woman-boy": {"a": "family: man, woman, boy", "b": "1F468-200D-1F469-200D-1F466", "j": ["boy", "family", "man", "woman"]}, "family-man-woman-girl": {"a": "family: man, woman, girl", "b": "1F468-200D-1F469-200D-1F467", "j": ["family", "girl", "man", "woman"]}, "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"]}, "family-man-woman-boy-boy": {"a": "family: man, woman, boy, boy", "b": "1F468-200D-1F469-200D-1F466-200D-1F466", "j": ["boy", "family", "man", "woman"]}, "family-man-woman-girl-girl": {"a": "family: man, woman, girl, girl", "b": "1F468-200D-1F469-200D-1F467-200D-1F467", "j": ["family", "girl", "man", "woman"]}, "family-man-man-boy": {"a": "family: man, man, boy", "b": "1F468-200D-1F468-200D-1F466", "j": ["boy", "family", "man"]}, "family-man-man-girl": {"a": "family: man, man, girl", "b": "1F468-200D-1F468-200D-1F467", "j": ["family", "girl", "man"]}, "family-man-man-girl-boy": {"a": "family: man, man, girl, boy", "b": "1F468-200D-1F468-200D-1F467-200D-1F466", "j": ["boy", "family", "girl", "man"]}, "family-man-man-boy-boy": {"a": "family: man, man, boy, boy", "b": "1F468-200D-1F468-200D-1F466-200D-1F466", "j": ["boy", "family", "man"]}, "family-man-man-girl-girl": {"a": "family: man, man, girl, girl", "b": "1F468-200D-1F468-200D-1F467-200D-1F467", "j": ["family", "girl", "man"]}, "family-woman-woman-boy": {"a": "family: woman, woman, boy", "b": "1F469-200D-1F469-200D-1F466", "j": ["boy", "family", "woman"]}, "family-woman-woman-girl": {"a": "family: woman, woman, girl", "b": "1F469-200D-1F469-200D-1F467", "j": ["family", "girl", "woman"]}, "family-woman-woman-girl-boy": {"a": "family: woman, woman, girl, boy", "b": "1F469-200D-1F469-200D-1F467-200D-1F466", "j": ["boy", "family", "girl", "woman"]}, "family-woman-woman-boy-boy": {"a": "family: woman, woman, boy, boy", "b": "1F469-200D-1F469-200D-1F466-200D-1F466", "j": ["boy", "family", "woman"]}, "family-woman-woman-girl-girl": {"a": "family: woman, woman, girl, girl", "b": "1F469-200D-1F469-200D-1F467-200D-1F467", "j": ["family", "girl", "woman"]}, "family-man-boy": {"a": "family: man, boy", "b": "1F468-200D-1F466", "j": ["boy", "family", "man"]}, "family-man-boy-boy": {"a": "family: man, boy, boy", "b": "1F468-200D-1F466-200D-1F466", "j": ["boy", "family", "man"]}, "family-man-girl": {"a": "family: man, girl", "b": "1F468-200D-1F467", "j": ["family", "girl", "man"]}, "family-man-girl-boy": {"a": "family: man, girl, boy", "b": "1F468-200D-1F467-200D-1F466", "j": ["boy", "family", "girl", "man"]}, "family-man-girl-girl": {"a": "family: man, girl, girl", "b": "1F468-200D-1F467-200D-1F467", "j": ["family", "girl", "man"]}, "family-woman-boy": {"a": "family: woman, boy", "b": "1F469-200D-1F466", "j": ["boy", "family", "woman"]}, "family-woman-boy-boy": {"a": "family: woman, boy, boy", "b": "1F469-200D-1F466-200D-1F466", "j": ["boy", "family", "woman"]}, "family-woman-girl": {"a": "family: woman, girl", "b": "1F469-200D-1F467", "j": ["family", "girl", "woman"]}, "family-woman-girl-boy": {"a": "family: woman, girl, boy", "b": "1F469-200D-1F467-200D-1F466", "j": ["boy", "family", "girl", "woman"]}, "family-woman-girl-girl": {"a": "family: woman, girl, girl", "b": "1F469-200D-1F467-200D-1F467", "j": ["family", "girl", "woman"]}, "speaking-head": {"a": "speaking head", "b": "1F5E3", "j": ["face", "head", "silhouette", "speak", "speaking"]}, "bust-in-silhouette": {"a": "bust in silhouette", "b": "1F464", "j": ["bust", "bust in silhouette", "silhouette"]}, "busts-in-silhouette": {"a": "busts in silhouette", "b": "1F465", "j": ["bust", "busts in silhouette", "silhouette"]}, "people-hugging": {"a": "people hugging", "b": "1FAC2", "j": ["goodbye", "hello", "hug", "people hugging", "thanks"]}, "footprints": {"a": "footprints", "b": "1F463", "j": ["clothing", "footprint", "footprints", "print"]}, "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"]}, "monkey": {"a": "monkey", "b": "1F412", "j": ["monkey"]}, "gorilla": {"a": "gorilla", "b": "1F98D", "j": ["gorilla"]}, "orangutan": {"a": "orangutan", "b": "1F9A7", "j": ["ape", "orangutan"]}, "dog-face": {"a": "dog face", "b": "1F436", "j": ["dog", "face", "pet"]}, "dog": {"a": "dog", "b": "1F415", "j": ["dog", "pet"]}, "guide-dog": {"a": "guide dog", "b": "1F9AE", "j": ["accessibility", "blind", "guide", "guide dog"]}, "service-dog": {"a": "service dog", "b": "1F415-200D-1F9BA", "j": ["accessibility", "assistance", "dog", "service"]}, "poodle": {"a": "poodle", "b": "1F429", "j": ["dog", "poodle"]}, "wolf": {"a": "wolf", "b": "1F43A", "j": ["face", "wolf"]}, "fox": {"a": "fox", "b": "1F98A", "j": ["face", "fox"]}, "raccoon": {"a": "raccoon", "b": "1F99D", "j": ["curious", "raccoon", "sly"]}, "cat-face": {"a": "cat face", "b": "1F431", "j": ["cat", "face", "pet"]}, "cat": {"a": "cat", "b": "1F408", "j": ["cat", "pet"]}, "black-cat": {"a": "black cat", "b": "1F408-200D-2B1B", "j": ["black", "cat", "unlucky"]}, "lion": {"a": "lion", "b": "1F981", "j": ["face", "Leo", "lion", "zodiac"]}, "tiger-face": {"a": "tiger face", "b": "1F42F", "j": ["face", "tiger"]}, "tiger": {"a": "tiger", "b": "1F405", "j": ["tiger"]}, "leopard": {"a": "leopard", "b": "1F406", "j": ["leopard"]}, "horse-face": {"a": "horse face", "b": "1F434", "j": ["face", "horse"]}, "horse": {"a": "horse", "b": "1F40E", "j": ["equestrian", "horse", "racehorse", "racing"]}, "unicorn": {"a": "unicorn", "b": "1F984", "j": ["face", "unicorn"]}, "zebra": {"a": "zebra", "b": "1F993", "j": ["stripe", "zebra"]}, "deer": {"a": "deer", "b": "1F98C", "j": ["deer"]}, "bison": {"a": "bison", "b": "1F9AC", "j": ["bison", "buffalo", "herd", "wisent"]}, "cow-face": {"a": "cow face", "b": "1F42E", "j": ["cow", "face"]}, "ox": {"a": "ox", "b": "1F402", "j": ["bull", "ox", "Taurus", "zodiac"]}, "water-buffalo": {"a": "water buffalo", "b": "1F403", "j": ["buffalo", "water"]}, "cow": {"a": "cow", "b": "1F404", "j": ["cow"]}, "pig-face": {"a": "pig face", "b": "1F437", "j": ["face", "pig"]}, "pig": {"a": "pig", "b": "1F416", "j": ["pig", "sow"]}, "boar": {"a": "boar", "b": "1F417", "j": ["boar", "pig"]}, "pig-nose": {"a": "pig nose", "b": "1F43D", "j": ["face", "nose", "pig"]}, "ram": {"a": "ram", "b": "1F40F", "j": ["Aries", "male", "ram", "sheep", "zodiac"]}, "ewe": {"a": "ewe", "b": "1F411", "j": ["ewe", "female", "sheep"]}, "goat": {"a": "goat", "b": "1F410", "j": ["Capricorn", "goat", "zodiac"]}, "camel": {"a": "camel", "b": "1F42A", "j": ["camel", "dromedary", "hump"]}, "twohump-camel": {"a": "two-hump camel", "b": "1F42B", "j": ["bactrian", "camel", "hump", "two-hump camel"]}, "llama": {"a": "llama", "b": "1F999", "j": ["alpaca", "guanaco", "llama", "vicuña", "wool"]}, "giraffe": {"a": "giraffe", "b": "1F992", "j": ["giraffe", "spots"]}, "elephant": {"a": "elephant", "b": "1F418", "j": ["elephant"]}, "mammoth": {"a": "mammoth", "b": "1F9A3", "j": ["extinction", "large", "mammoth", "tusk", "woolly"]}, "rhinoceros": {"a": "rhinoceros", "b": "1F98F", "j": ["rhinoceros"]}, "hippopotamus": {"a": "hippopotamus", "b": "1F99B", "j": ["hippo", "hippopotamus"]}, "mouse-face": {"a": "mouse face", "b": "1F42D", "j": ["face", "mouse"]}, "mouse": {"a": "mouse", "b": "1F401", "j": ["mouse"]}, "rat": {"a": "rat", "b": "1F400", "j": ["rat"]}, "hamster": {"a": "hamster", "b": "1F439", "j": ["face", "hamster", "pet"]}, "rabbit-face": {"a": "rabbit face", "b": "1F430", "j": ["bunny", "face", "pet", "rabbit"]}, "rabbit": {"a": "rabbit", "b": "1F407", "j": ["bunny", "pet", "rabbit"]}, "chipmunk": {"a": "chipmunk", "b": "1F43F", "j": ["chipmunk", "squirrel"]}, "beaver": {"a": "beaver", "b": "1F9AB", "j": ["beaver", "dam"]}, "hedgehog": {"a": "hedgehog", "b": "1F994", "j": ["hedgehog", "spiny"]}, "bat": {"a": "bat", "b": "1F987", "j": ["bat", "vampire"]}, "bear": {"a": "bear", "b": "1F43B", "j": ["bear", "face"]}, "polar-bear": {"a": "polar bear", "b": "1F43B-200D-2744-FE0F", "j": ["arctic", "bear", "polar bear", "white"]}, "koala": {"a": "koala", "b": "1F428", "j": ["bear", "koala"]}, "panda": {"a": "panda", "b": "1F43C", "j": ["face", "panda"]}, "sloth": {"a": "sloth", "b": "1F9A5", "j": ["lazy", "sloth", "slow"]}, "otter": {"a": "otter", "b": "1F9A6", "j": ["fishing", "otter", "playful"]}, "skunk": {"a": "skunk", "b": "1F9A8", "j": ["skunk", "stink"]}, "kangaroo": {"a": "kangaroo", "b": "1F998", "j": ["Australia", "joey", "jump", "kangaroo", "marsupial"]}, "badger": {"a": "badger", "b": "1F9A1", "j": ["badger", "honey badger", "pester"]}, "paw-prints": {"a": "paw prints", "b": "1F43E", "j": ["feet", "paw", "paw prints", "print"]}, "turkey": {"a": "turkey", "b": "1F983", "j": ["bird", "turkey"]}, "chicken": {"a": "chicken", "b": "1F414", "j": ["bird", "chicken"]}, "rooster": {"a": "rooster", "b": "1F413", "j": ["bird", "rooster"]}, "hatching-chick": {"a": "hatching chick", "b": "1F423", "j": ["baby", "bird", "chick", "hatching"]}, "baby-chick": {"a": "baby chick", "b": "1F424", "j": ["baby", "bird", "chick"]}, "frontfacing-baby-chick": {"a": "front-facing baby chick", "b": "1F425", "j": ["baby", "bird", "chick", "front-facing baby chick"]}, "bird": {"a": "bird", "b": "1F426", "j": ["bird"]}, "penguin": {"a": "penguin", "b": "1F427", "j": ["bird", "penguin"]}, "dove": {"a": "dove", "b": "1F54A", "j": ["bird", "dove", "fly", "peace"]}, "eagle": {"a": "eagle", "b": "1F985", "j": ["bird", "eagle"]}, "duck": {"a": "duck", "b": "1F986", "j": ["bird", "duck"]}, "swan": {"a": "swan", "b": "1F9A2", "j": ["bird", "cygnet", "swan", "ugly duckling"]}, "owl": {"a": "owl", "b": "1F989", "j": ["bird", "owl", "wise"]}, "dodo": {"a": "dodo", "b": "1F9A4", "j": ["dodo", "extinction", "large", "Mauritius"]}, "feather": {"a": "feather", "b": "1FAB6", "j": ["bird", "feather", "flight", "light", "plumage"]}, "flamingo": {"a": "flamingo", "b": "1F9A9", "j": ["flamboyant", "flamingo", "tropical"]}, "peacock": {"a": "peacock", "b": "1F99A", "j": ["bird", "ostentatious", "peacock", "peahen", "proud"]}, "parrot": {"a": "parrot", "b": "1F99C", "j": ["bird", "parrot", "pirate", "talk"]}, "frog": {"a": "frog", "b": "1F438", "j": ["face", "frog"]}, "crocodile": {"a": "crocodile", "b": "1F40A", "j": ["crocodile"]}, "turtle": {"a": "turtle", "b": "1F422", "j": ["terrapin", "tortoise", "turtle"]}, "lizard": {"a": "lizard", "b": "1F98E", "j": ["lizard", "reptile"]}, "snake": {"a": "snake", "b": "1F40D", "j": ["bearer", "Ophiuchus", "serpent", "snake", "zodiac"]}, "dragon-face": {"a": "dragon face", "b": "1F432", "j": ["dragon", "face", "fairy tale"]}, "dragon": {"a": "dragon", "b": "1F409", "j": ["dragon", "fairy tale"]}, "sauropod": {"a": "sauropod", "b": "1F995", "j": ["brachiosaurus", "brontosaurus", "diplodocus", "sauropod"]}, "trex": {"a": "T-Rex", "b": "1F996", "j": ["T-Rex", "Tyrannosaurus Rex"]}, "spouting-whale": {"a": "spouting whale", "b": "1F433", "j": ["face", "spouting", "whale"]}, "whale": {"a": "whale", "b": "1F40B", "j": ["whale"]}, "dolphin": {"a": "dolphin", "b": "1F42C", "j": ["dolphin", "flipper"]}, "seal": {"a": "seal", "b": "1F9AD", "j": ["sea Lion", "seal"]}, "fish": {"a": "fish", "b": "1F41F", "j": ["fish", "Pisces", "zodiac"]}, "tropical-fish": {"a": "tropical fish", "b": "1F420", "j": ["fish", "tropical"]}, "blowfish": {"a": "blowfish", "b": "1F421", "j": ["blowfish", "fish"]}, "shark": {"a": "shark", "b": "1F988", "j": ["fish", "shark"]}, "octopus": {"a": "octopus", "b": "1F419", "j": ["octopus"]}, "spiral-shell": {"a": "spiral shell", "b": "1F41A", "j": ["shell", "spiral"]}, "snail": {"a": "snail", "b": "1F40C", "j": ["snail"]}, "butterfly": {"a": "butterfly", "b": "1F98B", "j": ["butterfly", "insect", "pretty"]}, "bug": {"a": "bug", "b": "1F41B", "j": ["bug", "insect"]}, "ant": {"a": "ant", "b": "1F41C", "j": ["ant", "insect"]}, "honeybee": {"a": "honeybee", "b": "1F41D", "j": ["bee", "honeybee", "insect"]}, "beetle": {"a": "beetle", "b": "1FAB2", "j": ["beetle", "bug", "insect"]}, "lady-beetle": {"a": "lady beetle", "b": "1F41E", "j": ["beetle", "insect", "lady beetle", "ladybird", "ladybug"]}, "cricket": {"a": "cricket", "b": "1F997", "j": ["cricket", "grasshopper", "Orthoptera"]}, "cockroach": {"a": "cockroach", "b": "1FAB3", "j": ["cockroach", "insect", "pest", "roach"]}, "spider": {"a": "spider", "b": "1F577", "j": ["insect", "spider"]}, "spider-web": {"a": "spider web", "b": "1F578", "j": ["spider", "web"]}, "scorpion": {"a": "scorpion", "b": "1F982", "j": ["scorpio", "Scorpio", "scorpion", "zodiac"]}, "mosquito": {"a": "mosquito", "b": "1F99F", "j": ["disease", "fever", "malaria", "mosquito", "pest", "virus"]}, "fly": {"a": "fly", "b": "1FAB0", "j": ["disease", "fly", "maggot", "pest", "rotting"]}, "worm": {"a": "worm", "b": "1FAB1", "j": ["annelid", "earthworm", "parasite", "worm"]}, "microbe": {"a": "microbe", "b": "1F9A0", "j": ["amoeba", "bacteria", "microbe", "virus"]}, "bouquet": {"a": "bouquet", "b": "1F490", "j": ["bouquet", "flower"]}, "cherry-blossom": {"a": "cherry blossom", "b": "1F338", "j": ["blossom", "cherry", "flower"]}, "white-flower": {"a": "white flower", "b": "1F4AE", "j": ["flower", "white flower"]}, "rosette": {"a": "rosette", "b": "1F3F5", "j": ["plant", "rosette"]}, "rose": {"a": "rose", "b": "1F339", "j": ["flower", "rose"]}, "wilted-flower": {"a": "wilted flower", "b": "1F940", "j": ["flower", "wilted"]}, "hibiscus": {"a": "hibiscus", "b": "1F33A", "j": ["flower", "hibiscus"]}, "sunflower": {"a": "sunflower", "b": "1F33B", "j": ["flower", "sun", "sunflower"]}, "blossom": {"a": "blossom", "b": "1F33C", "j": ["blossom", "flower"]}, "tulip": {"a": "tulip", "b": "1F337", "j": ["flower", "tulip"]}, "seedling": {"a": "seedling", "b": "1F331", "j": ["seedling", "young"]}, "potted-plant": {"a": "potted plant", "b": "1FAB4", "j": ["boring", "grow", "house", "nurturing", "plant", "potted plant", "useless"]}, "evergreen-tree": {"a": "evergreen tree", "b": "1F332", "j": ["evergreen tree", "tree"]}, "deciduous-tree": {"a": "deciduous tree", "b": "1F333", "j": ["deciduous", "shedding", "tree"]}, "palm-tree": {"a": "palm tree", "b": "1F334", "j": ["palm", "tree"]}, "cactus": {"a": "cactus", "b": "1F335", "j": ["cactus", "plant"]}, "sheaf-of-rice": {"a": "sheaf of rice", "b": "1F33E", "j": ["ear", "grain", "rice", "sheaf of rice"]}, "herb": {"a": "herb", "b": "1F33F", "j": ["herb", "leaf"]}, "shamrock": {"a": "shamrock", "b": "2618", "j": ["plant", "shamrock"]}, "four-leaf-clover": {"a": "four leaf clover", "b": "1F340", "j": ["4", "clover", "four", "four-leaf clover", "leaf"]}, "maple-leaf": {"a": "maple leaf", "b": "1F341", "j": ["falling", "leaf", "maple"]}, "fallen-leaf": {"a": "fallen leaf", "b": "1F342", "j": ["fallen leaf", "falling", "leaf"]}, "leaf-fluttering-in-wind": {"a": "leaf fluttering in wind", "b": "1F343", "j": ["blow", "flutter", "leaf", "leaf fluttering in wind", "wind"]}, "grapes": {"a": "grapes", "b": "1F347", "j": ["fruit", "grape", "grapes"]}, "melon": {"a": "melon", "b": "1F348", "j": ["fruit", "melon"]}, "watermelon": {"a": "watermelon", "b": "1F349", "j": ["fruit", "watermelon"]}, "tangerine": {"a": "tangerine", "b": "1F34A", "j": ["fruit", "orange", "tangerine"]}, "lemon": {"a": "lemon", "b": "1F34B", "j": ["citrus", "fruit", "lemon"]}, "banana": {"a": "banana", "b": "1F34C", "j": ["banana", "fruit"]}, "pineapple": {"a": "pineapple", "b": "1F34D", "j": ["fruit", "pineapple"]}, "mango": {"a": "mango", "b": "1F96D", "j": ["fruit", "mango", "tropical"]}, "red-apple": {"a": "red apple", "b": "1F34E", "j": ["apple", "fruit", "red"]}, "green-apple": {"a": "green apple", "b": "1F34F", "j": ["apple", "fruit", "green"]}, "pear": {"a": "pear", "b": "1F350", "j": ["fruit", "pear"]}, "peach": {"a": "peach", "b": "1F351", "j": ["fruit", "peach"]}, "cherries": {"a": "cherries", "b": "1F352", "j": ["berries", "cherries", "cherry", "fruit", "red"]}, "strawberry": {"a": "strawberry", "b": "1F353", "j": ["berry", "fruit", "strawberry"]}, "blueberries": {"a": "blueberries", "b": "1FAD0", "j": ["berry", "bilberry", "blue", "blueberries", "blueberry"]}, "kiwi-fruit": {"a": "kiwi fruit", "b": "1F95D", "j": ["food", "fruit", "kiwi"]}, "tomato": {"a": "tomato", "b": "1F345", "j": ["fruit", "tomato", "vegetable"]}, "olive": {"a": "olive", "b": "1FAD2", "j": ["food", "olive"]}, "coconut": {"a": "coconut", "b": "1F965", "j": ["coconut", "palm", "piña colada"]}, "avocado": {"a": "avocado", "b": "1F951", "j": ["avocado", "food", "fruit"]}, "eggplant": {"a": "eggplant", "b": "1F346", "j": ["aubergine", "eggplant", "vegetable"]}, "potato": {"a": "potato", "b": "1F954", "j": ["food", "potato", "vegetable"]}, "carrot": {"a": "carrot", "b": "1F955", "j": ["carrot", "food", "vegetable"]}, "ear-of-corn": {"a": "ear of corn", "b": "1F33D", "j": ["corn", "ear", "ear of corn", "maize", "maze"]}, "hot-pepper": {"a": "hot pepper", "b": "1F336", "j": ["hot", "pepper"]}, "bell-pepper": {"a": "bell pepper", "b": "1FAD1", "j": ["bell pepper", "capsicum", "pepper", "vegetable"]}, "cucumber": {"a": "cucumber", "b": "1F952", "j": ["cucumber", "food", "pickle", "vegetable"]}, "leafy-green": {"a": "leafy green", "b": "1F96C", "j": ["bok choy", "cabbage", "kale", "leafy green", "lettuce"]}, "broccoli": {"a": "broccoli", "b": "1F966", "j": ["broccoli", "wild cabbage"]}, "garlic": {"a": "garlic", "b": "1F9C4", "j": ["flavoring", "garlic"]}, "onion": {"a": "onion", "b": "1F9C5", "j": ["flavoring", "onion"]}, "mushroom": {"a": "mushroom", "b": "1F344", "j": ["mushroom", "toadstool"]}, "peanuts": {"a": "peanuts", "b": "1F95C", "j": ["food", "nut", "peanut", "peanuts", "vegetable"]}, "chestnut": {"a": "chestnut", "b": "1F330", "j": ["chestnut", "plant"]}, "bread": {"a": "bread", "b": "1F35E", "j": ["bread", "loaf"]}, "croissant": {"a": "croissant", "b": "1F950", "j": ["bread", "breakfast", "croissant", "food", "french", "roll"]}, "baguette-bread": {"a": "baguette bread", "b": "1F956", "j": ["baguette", "bread", "food", "french"]}, "flatbread": {"a": "flatbread", "b": "1FAD3", "j": ["arepa", "flatbread", "lavash", "naan", "pita"]}, "pretzel": {"a": "pretzel", "b": "1F968", "j": ["pretzel", "twisted", "convoluted"]}, "bagel": {"a": "bagel", "b": "1F96F", "j": ["bagel", "bakery", "breakfast", "schmear"]}, "pancakes": {"a": "pancakes", "b": "1F95E", "j": ["breakfast", "crêpe", "food", "hotcake", "pancake", "pancakes"]}, "waffle": {"a": "waffle", "b": "1F9C7", "j": ["breakfast", "indecisive", "iron", "waffle"]}, "cheese-wedge": {"a": "cheese wedge", "b": "1F9C0", "j": ["cheese", "cheese wedge"]}, "meat-on-bone": {"a": "meat on bone", "b": "1F356", "j": ["bone", "meat", "meat on bone"]}, "poultry-leg": {"a": "poultry leg", "b": "1F357", "j": ["bone", "chicken", "drumstick", "leg", "poultry"]}, "cut-of-meat": {"a": "cut of meat", "b": "1F969", "j": ["chop", "cut of meat", "lambchop", "porkchop", "steak"]}, "bacon": {"a": "bacon", "b": "1F953", "j": ["bacon", "breakfast", "food", "meat"]}, "hamburger": {"a": "hamburger", "b": "1F354", "j": ["burger", "hamburger"]}, "french-fries": {"a": "french fries", "b": "1F35F", "j": ["french", "fries"]}, "pizza": {"a": "pizza", "b": "1F355", "j": ["cheese", "pizza", "slice"]}, "hot-dog": {"a": "hot dog", "b": "1F32D", "j": ["frankfurter", "hot dog", "hotdog", "sausage"]}, "sandwich": {"a": "sandwich", "b": "1F96A", "j": ["bread", "sandwich"]}, "taco": {"a": "taco", "b": "1F32E", "j": ["mexican", "taco"]}, "burrito": {"a": "burrito", "b": "1F32F", "j": ["burrito", "mexican", "wrap"]}, "tamale": {"a": "tamale", "b": "1FAD4", "j": ["mexican", "tamale", "wrapped"]}, "stuffed-flatbread": {"a": "stuffed flatbread", "b": "1F959", "j": ["falafel", "flatbread", "food", "gyro", "kebab", "stuffed"]}, "falafel": {"a": "falafel", "b": "1F9C6", "j": ["chickpea", "falafel", "meatball"]}, "egg": {"a": "egg", "b": "1F95A", "j": ["breakfast", "egg", "food"]}, "cooking": {"a": "cooking", "b": "1F373", "j": ["breakfast", "cooking", "egg", "frying", "pan"]}, "shallow-pan-of-food": {"a": "shallow pan of food", "b": "1F958", "j": ["casserole", "food", "paella", "pan", "shallow", "shallow pan of food"]}, "pot-of-food": {"a": "pot of food", "b": "1F372", "j": ["pot", "pot of food", "stew"]}, "fondue": {"a": "fondue", "b": "1FAD5", "j": ["cheese", "chocolate", "fondue", "melted", "pot", "Swiss"]}, "bowl-with-spoon": {"a": "bowl with spoon", "b": "1F963", "j": ["bowl with spoon", "breakfast", "cereal", "congee", "oatmeal", "porridge"]}, "green-salad": {"a": "green salad", "b": "1F957", "j": ["food", "green", "salad"]}, "popcorn": {"a": "popcorn", "b": "1F37F", "j": ["popcorn"]}, "butter": {"a": "butter", "b": "1F9C8", "j": ["butter", "dairy"]}, "salt": {"a": "salt", "b": "1F9C2", "j": ["condiment", "salt", "shaker"]}, "canned-food": {"a": "canned food", "b": "1F96B", "j": ["can", "canned food"]}, "bento-box": {"a": "bento box", "b": "1F371", "j": ["bento", "box"]}, "rice-cracker": {"a": "rice cracker", "b": "1F358", "j": ["cracker", "rice"]}, "rice-ball": {"a": "rice ball", "b": "1F359", "j": ["ball", "Japanese", "rice"]}, "cooked-rice": {"a": "cooked rice", "b": "1F35A", "j": ["cooked", "rice"]}, "curry-rice": {"a": "curry rice", "b": "1F35B", "j": ["curry", "rice"]}, "steaming-bowl": {"a": "steaming bowl", "b": "1F35C", "j": ["bowl", "noodle", "ramen", "steaming"]}, "spaghetti": {"a": "spaghetti", "b": "1F35D", "j": ["pasta", "spaghetti"]}, "roasted-sweet-potato": {"a": "roasted sweet potato", "b": "1F360", "j": ["potato", "roasted", "sweet"]}, "oden": {"a": "oden", "b": "1F362", "j": ["kebab", "oden", "seafood", "skewer", "stick"]}, "sushi": {"a": "sushi", "b": "1F363", "j": ["sushi"]}, "fried-shrimp": {"a": "fried shrimp", "b": "1F364", "j": ["fried", "prawn", "shrimp", "tempura"]}, "fish-cake-with-swirl": {"a": "fish cake with swirl", "b": "1F365", "j": ["cake", "fish", "fish cake with swirl", "pastry", "swirl"]}, "moon-cake": {"a": "moon cake", "b": "1F96E", "j": ["autumn", "festival", "moon cake", "yuèbǐng"]}, "dango": {"a": "dango", "b": "1F361", "j": ["dango", "dessert", "Japanese", "skewer", "stick", "sweet"]}, "dumpling": {"a": "dumpling", "b": "1F95F", "j": ["dumpling", "empanada", "gyōza", "jiaozi", "pierogi", "potsticker"]}, "fortune-cookie": {"a": "fortune cookie", "b": "1F960", "j": ["fortune cookie", "prophecy"]}, "takeout-box": {"a": "takeout box", "b": "1F961", "j": ["oyster pail", "takeout box"]}, "crab": {"a": "crab", "b": "1F980", "j": ["Cancer", "crab", "zodiac"]}, "lobster": {"a": "lobster", "b": "1F99E", "j": ["bisque", "claws", "lobster", "seafood"]}, "shrimp": {"a": "shrimp", "b": "1F990", "j": ["food", "shellfish", "shrimp", "small"]}, "squid": {"a": "squid", "b": "1F991", "j": ["food", "molusc", "squid"]}, "oyster": {"a": "oyster", "b": "1F9AA", "j": ["diving", "oyster", "pearl"]}, "soft-ice-cream": {"a": "soft ice cream", "b": "1F366", "j": ["cream", "dessert", "ice", "icecream", "soft", "sweet"]}, "shaved-ice": {"a": "shaved ice", "b": "1F367", "j": ["dessert", "ice", "shaved", "sweet"]}, "ice-cream": {"a": "ice cream", "b": "1F368", "j": ["cream", "dessert", "ice", "sweet"]}, "doughnut": {"a": "doughnut", "b": "1F369", "j": ["breakfast", "dessert", "donut", "doughnut", "sweet"]}, "cookie": {"a": "cookie", "b": "1F36A", "j": ["cookie", "dessert", "sweet"]}, "birthday-cake": {"a": "birthday cake", "b": "1F382", "j": ["birthday", "cake", "celebration", "dessert", "pastry", "sweet"]}, "shortcake": {"a": "shortcake", "b": "1F370", "j": ["cake", "dessert", "pastry", "shortcake", "slice", "sweet"]}, "cupcake": {"a": "cupcake", "b": "1F9C1", "j": ["bakery", "cupcake", "sweet"]}, "pie": {"a": "pie", "b": "1F967", "j": ["filling", "pastry", "pie", "fruit", "meat"]}, "chocolate-bar": {"a": "chocolate bar", "b": "1F36B", "j": ["bar", "chocolate", "dessert", "sweet"]}, "candy": {"a": "candy", "b": "1F36C", "j": ["candy", "dessert", "sweet"]}, "lollipop": {"a": "lollipop", "b": "1F36D", "j": ["candy", "dessert", "lollipop", "sweet"]}, "custard": {"a": "custard", "b": "1F36E", "j": ["custard", "dessert", "pudding", "sweet"]}, "honey-pot": {"a": "honey pot", "b": "1F36F", "j": ["honey", "honeypot", "pot", "sweet"]}, "baby-bottle": {"a": "baby bottle", "b": "1F37C", "j": ["baby", "bottle", "drink", "milk"]}, "glass-of-milk": {"a": "glass of milk", "b": "1F95B", "j": ["drink", "glass", "glass of milk", "milk"]}, "hot-beverage": {"a": "hot beverage", "b": "2615", "j": ["beverage", "coffee", "drink", "hot", "steaming", "tea"]}, "teapot": {"a": "teapot", "b": "1FAD6", "j": ["drink", "pot", "tea", "teapot"]}, "teacup-without-handle": {"a": "teacup without handle", "b": "1F375", "j": ["beverage", "cup", "drink", "tea", "teacup", "teacup without handle"]}, "sake": {"a": "sake", "b": "1F376", "j": ["bar", "beverage", "bottle", "cup", "drink", "sake"]}, "bottle-with-popping-cork": {"a": "bottle with popping cork", "b": "1F37E", "j": ["bar", "bottle", "bottle with popping cork", "cork", "drink", "popping"]}, "wine-glass": {"a": "wine glass", "b": "1F377", "j": ["bar", "beverage", "drink", "glass", "wine"]}, "cocktail-glass": {"a": "cocktail glass", "b": "1F378", "j": ["bar", "cocktail", "drink", "glass"]}, "tropical-drink": {"a": "tropical drink", "b": "1F379", "j": ["bar", "drink", "tropical"]}, "beer-mug": {"a": "beer mug", "b": "1F37A", "j": ["bar", "beer", "drink", "mug"]}, "clinking-beer-mugs": {"a": "clinking beer mugs", "b": "1F37B", "j": ["bar", "beer", "clink", "clinking beer mugs", "drink", "mug"]}, "clinking-glasses": {"a": "clinking glasses", "b": "1F942", "j": ["celebrate", "clink", "clinking glasses", "drink", "glass"]}, "tumbler-glass": {"a": "tumbler glass", "b": "1F943", "j": ["glass", "liquor", "shot", "tumbler", "whisky"]}, "cup-with-straw": {"a": "cup with straw", "b": "1F964", "j": ["cup with straw", "juice", "soda", "malt", "soft drink", "water"]}, "bubble-tea": {"a": "bubble tea", "b": "1F9CB", "j": ["bubble", "milk", "pearl", "tea"]}, "beverage-box": {"a": "beverage box", "b": "1F9C3", "j": ["beverage", "box", "juice", "straw", "sweet"]}, "mate": {"a": "mate", "b": "1F9C9", "j": ["drink", "mate"]}, "ice": {"a": "ice", "b": "1F9CA", "j": ["cold", "ice", "ice cube", "iceberg"]}, "chopsticks": {"a": "chopsticks", "b": "1F962", "j": ["chopsticks", "hashi", "jeotgarak", "kuaizi"]}, "fork-and-knife-with-plate": {"a": "fork and knife with plate", "b": "1F37D", "j": ["cooking", "fork", "fork and knife with plate", "knife", "plate"]}, "fork-and-knife": {"a": "fork and knife", "b": "1F374", "j": ["cooking", "cutlery", "fork", "fork and knife", "knife"]}, "spoon": {"a": "spoon", "b": "1F944", "j": ["spoon", "tableware"]}, "kitchen-knife": {"a": "kitchen knife", "b": "1F52A", "j": ["cooking", "hocho", "kitchen knife", "knife", "tool", "weapon"]}, "amphora": {"a": "amphora", "b": "1F3FA", "j": ["amphora", "Aquarius", "cooking", "drink", "jug", "zodiac"]}, "globe-showing-europeafrica": {"a": "globe showing Europe-Africa", "b": "1F30D", "j": ["Africa", "earth", "Europe", "globe", "globe showing Europe-Africa", "world"]}, "globe-showing-americas": {"a": "globe showing Americas", "b": "1F30E", "j": ["Americas", "earth", "globe", "globe showing Americas", "world"]}, "globe-showing-asiaaustralia": {"a": "globe showing Asia-Australia", "b": "1F30F", "j": ["Asia", "Australia", "earth", "globe", "globe showing Asia-Australia", "world"]}, "globe-with-meridians": {"a": "globe with meridians", "b": "1F310", "j": ["earth", "globe", "globe with meridians", "meridians", "world"]}, "world-map": {"a": "world map", "b": "1F5FA", "j": ["map", "world"]}, "map-of-japan": {"a": "map of Japan", "b": "1F5FE", "j": ["Japan", "map", "map of Japan"]}, "compass": {"a": "compass", "b": "1F9ED", "j": ["compass", "magnetic", "navigation", "orienteering"]}, "snowcapped-mountain": {"a": "snow-capped mountain", "b": "1F3D4", "j": ["cold", "mountain", "snow", "snow-capped mountain"]}, "mountain": {"a": "mountain", "b": "26F0", "j": ["mountain"]}, "volcano": {"a": "volcano", "b": "1F30B", "j": ["eruption", "mountain", "volcano"]}, "mount-fuji": {"a": "mount fuji", "b": "1F5FB", "j": ["fuji", "mount fuji", "mountain"]}, "camping": {"a": "camping", "b": "1F3D5", "j": ["camping"]}, "beach-with-umbrella": {"a": "beach with umbrella", "b": "1F3D6", "j": ["beach", "beach with umbrella", "umbrella"]}, "desert": {"a": "desert", "b": "1F3DC", "j": ["desert"]}, "desert-island": {"a": "desert island", "b": "1F3DD", "j": ["desert", "island"]}, "national-park": {"a": "national park", "b": "1F3DE", "j": ["national park", "park"]}, "stadium": {"a": "stadium", "b": "1F3DF", "j": ["stadium"]}, "classical-building": {"a": "classical building", "b": "1F3DB", "j": ["classical", "classical building"]}, "building-construction": {"a": "building construction", "b": "1F3D7", "j": ["building construction", "construction"]}, "brick": {"a": "brick", "b": "1F9F1", "j": ["brick", "bricks", "clay", "mortar", "wall"]}, "rock": {"a": "rock", "b": "1FAA8", "j": ["boulder", "heavy", "rock", "solid", "stone"]}, "wood": {"a": "wood", "b": "1FAB5", "j": ["log", "lumber", "timber", "wood"]}, "hut": {"a": "hut", "b": "1F6D6", "j": ["house", "hut", "roundhouse", "yurt"]}, "houses": {"a": "houses", "b": "1F3D8", "j": ["houses"]}, "derelict-house": {"a": "derelict house", "b": "1F3DA", "j": ["derelict", "house"]}, "house": {"a": "house", "b": "1F3E0", "j": ["home", "house"]}, "house-with-garden": {"a": "house with garden", "b": "1F3E1", "j": ["garden", "home", "house", "house with garden"]}, "office-building": {"a": "office building", "b": "1F3E2", "j": ["building", "office building"]}, "japanese-post-office": {"a": "Japanese post office", "b": "1F3E3", "j": ["Japanese", "Japanese post office", "post"]}, "post-office": {"a": "post office", "b": "1F3E4", "j": ["European", "post", "post office"]}, "hospital": {"a": "hospital", "b": "1F3E5", "j": ["doctor", "hospital", "medicine"]}, "bank": {"a": "bank", "b": "1F3E6", "j": ["bank", "building"]}, "hotel": {"a": "hotel", "b": "1F3E8", "j": ["building", "hotel"]}, "love-hotel": {"a": "love hotel", "b": "1F3E9", "j": ["hotel", "love"]}, "convenience-store": {"a": "convenience store", "b": "1F3EA", "j": ["convenience", "store"]}, "school": {"a": "school", "b": "1F3EB", "j": ["building", "school"]}, "department-store": {"a": "department store", "b": "1F3EC", "j": ["department", "store"]}, "factory": {"a": "factory", "b": "1F3ED", "j": ["building", "factory"]}, "japanese-castle": {"a": "Japanese castle", "b": "1F3EF", "j": ["castle", "Japanese"]}, "castle": {"a": "castle", "b": "1F3F0", "j": ["castle", "European"]}, "wedding": {"a": "wedding", "b": "1F492", "j": ["chapel", "romance", "wedding"]}, "tokyo-tower": {"a": "Tokyo tower", "b": "1F5FC", "j": ["Tokyo", "tower"]}, "statue-of-liberty": {"a": "Statue of Liberty", "b": "1F5FD", "j": ["liberty", "statue", "Statue of Liberty"]}, "church": {"a": "church", "b": "26EA", "j": ["Christian", "church", "cross", "religion"]}, "mosque": {"a": "mosque", "b": "1F54C", "j": ["islam", "mosque", "Muslim", "religion"]}, "hindu-temple": {"a": "hindu temple", "b": "1F6D5", "j": ["hindu", "temple"]}, "synagogue": {"a": "synagogue", "b": "1F54D", "j": ["Jew", "Jewish", "religion", "synagogue", "temple"]}, "shinto-shrine": {"a": "shinto shrine", "b": "26E9", "j": ["religion", "shinto", "shrine"]}, "kaaba": {"a": "kaaba", "b": "1F54B", "j": ["islam", "kaaba", "Muslim", "religion"]}, "fountain": {"a": "fountain", "b": "26F2", "j": ["fountain"]}, "tent": {"a": "tent", "b": "26FA", "j": ["camping", "tent"]}, "foggy": {"a": "foggy", "b": "1F301", "j": ["fog", "foggy"]}, "night-with-stars": {"a": "night with stars", "b": "1F303", "j": ["night", "night with stars", "star"]}, "cityscape": {"a": "cityscape", "b": "1F3D9", "j": ["city", "cityscape"]}, "sunrise-over-mountains": {"a": "sunrise over mountains", "b": "1F304", "j": ["morning", "mountain", "sun", "sunrise", "sunrise over mountains"]}, "sunrise": {"a": "sunrise", "b": "1F305", "j": ["morning", "sun", "sunrise"]}, "cityscape-at-dusk": {"a": "cityscape at dusk", "b": "1F306", "j": ["city", "cityscape at dusk", "dusk", "evening", "landscape", "sunset"]}, "sunset": {"a": "sunset", "b": "1F307", "j": ["dusk", "sun", "sunset"]}, "bridge-at-night": {"a": "bridge at night", "b": "1F309", "j": ["bridge", "bridge at night", "night"]}, "hot-springs": {"a": "hot springs", "b": "2668", "j": ["hot", "hotsprings", "springs", "steaming"]}, "carousel-horse": {"a": "carousel horse", "b": "1F3A0", "j": ["carousel", "horse"]}, "ferris-wheel": {"a": "ferris wheel", "b": "1F3A1", "j": ["amusement park", "ferris", "wheel"]}, "roller-coaster": {"a": "roller coaster", "b": "1F3A2", "j": ["amusement park", "coaster", "roller"]}, "barber-pole": {"a": "barber pole", "b": "1F488", "j": ["barber", "haircut", "pole"]}, "circus-tent": {"a": "circus tent", "b": "1F3AA", "j": ["circus", "tent"]}, "locomotive": {"a": "locomotive", "b": "1F682", "j": ["engine", "locomotive", "railway", "steam", "train"]}, "railway-car": {"a": "railway car", "b": "1F683", "j": ["car", "electric", "railway", "train", "tram", "trolleybus"]}, "highspeed-train": {"a": "high-speed train", "b": "1F684", "j": ["high-speed train", "railway", "shinkansen", "speed", "train"]}, "bullet-train": {"a": "bullet train", "b": "1F685", "j": ["bullet", "railway", "shinkansen", "speed", "train"]}, "train": {"a": "train", "b": "1F686", "j": ["railway", "train"]}, "metro": {"a": "metro", "b": "1F687", "j": ["metro", "subway"]}, "light-rail": {"a": "light rail", "b": "1F688", "j": ["light rail", "railway"]}, "station": {"a": "station", "b": "1F689", "j": ["railway", "station", "train"]}, "tram": {"a": "tram", "b": "1F68A", "j": ["tram", "trolleybus"]}, "monorail": {"a": "monorail", "b": "1F69D", "j": ["monorail", "vehicle"]}, "mountain-railway": {"a": "mountain railway", "b": "1F69E", "j": ["car", "mountain", "railway"]}, "tram-car": {"a": "tram car", "b": "1F68B", "j": ["car", "tram", "trolleybus"]}, "bus": {"a": "bus", "b": "1F68C", "j": ["bus", "vehicle"]}, "oncoming-bus": {"a": "oncoming bus", "b": "1F68D", "j": ["bus", "oncoming"]}, "trolleybus": {"a": "trolleybus", "b": "1F68E", "j": ["bus", "tram", "trolley", "trolleybus"]}, "minibus": {"a": "minibus", "b": "1F690", "j": ["bus", "minibus"]}, "ambulance": {"a": "ambulance", "b": "1F691", "j": ["ambulance", "vehicle"]}, "fire-engine": {"a": "fire engine", "b": "1F692", "j": ["engine", "fire", "truck"]}, "police-car": {"a": "police car", "b": "1F693", "j": ["car", "patrol", "police"]}, "oncoming-police-car": {"a": "oncoming police car", "b": "1F694", "j": ["car", "oncoming", "police"]}, "taxi": {"a": "taxi", "b": "1F695", "j": ["taxi", "vehicle"]}, "oncoming-taxi": {"a": "oncoming taxi", "b": "1F696", "j": ["oncoming", "taxi"]}, "automobile": {"a": "automobile", "b": "1F697", "j": ["automobile", "car"]}, "oncoming-automobile": {"a": "oncoming automobile", "b": "1F698", "j": ["automobile", "car", "oncoming"]}, "sport-utility-vehicle": {"a": "sport utility vehicle", "b": "1F699", "j": ["recreational", "sport utility", "sport utility vehicle"]}, "pickup-truck": {"a": "pickup truck", "b": "1F6FB", "j": ["pick-up", "pickup", "truck"]}, "delivery-truck": {"a": "delivery truck", "b": "1F69A", "j": ["delivery", "truck"]}, "articulated-lorry": {"a": "articulated lorry", "b": "1F69B", "j": ["articulated lorry", "lorry", "semi", "truck"]}, "tractor": {"a": "tractor", "b": "1F69C", "j": ["tractor", "vehicle"]}, "racing-car": {"a": "racing car", "b": "1F3CE", "j": ["car", "racing"]}, "motorcycle": {"a": "motorcycle", "b": "1F3CD", "j": ["motorcycle", "racing"]}, "motor-scooter": {"a": "motor scooter", "b": "1F6F5", "j": ["motor", "scooter"]}, "manual-wheelchair": {"a": "manual wheelchair", "b": "1F9BD", "j": ["accessibility", "manual wheelchair"]}, "motorized-wheelchair": {"a": "motorized wheelchair", "b": "1F9BC", "j": ["accessibility", "motorized wheelchair"]}, "auto-rickshaw": {"a": "auto rickshaw", "b": "1F6FA", "j": ["auto rickshaw", "tuk tuk"]}, "bicycle": {"a": "bicycle", "b": "1F6B2", "j": ["bicycle", "bike"]}, "kick-scooter": {"a": "kick scooter", "b": "1F6F4", "j": ["kick", "scooter"]}, "skateboard": {"a": "skateboard", "b": "1F6F9", "j": ["board", "skateboard"]}, "roller-skate": {"a": "roller skate", "b": "1F6FC", "j": ["roller", "skate"]}, "bus-stop": {"a": "bus stop", "b": "1F68F", "j": ["bus", "busstop", "stop"]}, "motorway": {"a": "motorway", "b": "1F6E3", "j": ["highway", "motorway", "road"]}, "railway-track": {"a": "railway track", "b": "1F6E4", "j": ["railway", "railway track", "train"]}, "oil-drum": {"a": "oil drum", "b": "1F6E2", "j": ["drum", "oil"]}, "fuel-pump": {"a": "fuel pump", "b": "26FD", "j": ["diesel", "fuel", "fuelpump", "gas", "pump", "station"]}, "police-car-light": {"a": "police car light", "b": "1F6A8", "j": ["beacon", "car", "light", "police", "revolving"]}, "horizontal-traffic-light": {"a": "horizontal traffic light", "b": "1F6A5", "j": ["horizontal traffic light", "light", "signal", "traffic"]}, "vertical-traffic-light": {"a": "vertical traffic light", "b": "1F6A6", "j": ["light", "signal", "traffic", "vertical traffic light"]}, "stop-sign": {"a": "stop sign", "b": "1F6D1", "j": ["octagonal", "sign", "stop"]}, "construction": {"a": "construction", "b": "1F6A7", "j": ["barrier", "construction"]}, "anchor": {"a": "anchor", "b": "2693", "j": ["anchor", "ship", "tool"]}, "sailboat": {"a": "sailboat", "b": "26F5", "j": ["boat", "resort", "sailboat", "sea", "yacht"]}, "canoe": {"a": "canoe", "b": "1F6F6", "j": ["boat", "canoe"]}, "speedboat": {"a": "speedboat", "b": "1F6A4", "j": ["boat", "speedboat"]}, "passenger-ship": {"a": "passenger ship", "b": "1F6F3", "j": ["passenger", "ship"]}, "ferry": {"a": "ferry", "b": "26F4", "j": ["boat", "ferry", "passenger"]}, "motor-boat": {"a": "motor boat", "b": "1F6E5", "j": ["boat", "motor boat", "motorboat"]}, "ship": {"a": "ship", "b": "1F6A2", "j": ["boat", "passenger", "ship"]}, "airplane": {"a": "airplane", "b": "2708", "j": ["aeroplane", "airplane"]}, "small-airplane": {"a": "small airplane", "b": "1F6E9", "j": ["aeroplane", "airplane", "small airplane"]}, "airplane-departure": {"a": "airplane departure", "b": "1F6EB", "j": ["aeroplane", "airplane", "check-in", "departure", "departures"]}, "airplane-arrival": {"a": "airplane arrival", "b": "1F6EC", "j": ["aeroplane", "airplane", "airplane arrival", "arrivals", "arriving", "landing"]}, "parachute": {"a": "parachute", "b": "1FA82", "j": ["hang-glide", "parachute", "parasail", "skydive"]}, "seat": {"a": "seat", "b": "1F4BA", "j": ["chair", "seat"]}, "helicopter": {"a": "helicopter", "b": "1F681", "j": ["helicopter", "vehicle"]}, "suspension-railway": {"a": "suspension railway", "b": "1F69F", "j": ["railway", "suspension"]}, "mountain-cableway": {"a": "mountain cableway", "b": "1F6A0", "j": ["cable", "gondola", "mountain", "mountain cableway"]}, "aerial-tramway": {"a": "aerial tramway", "b": "1F6A1", "j": ["aerial", "cable", "car", "gondola", "tramway"]}, "satellite": {"a": "satellite", "b": "1F6F0", "j": ["satellite", "space"]}, "rocket": {"a": "rocket", "b": "1F680", "j": ["rocket", "space"]}, "flying-saucer": {"a": "flying saucer", "b": "1F6F8", "j": ["flying saucer", "UFO"]}, "bellhop-bell": {"a": "bellhop bell", "b": "1F6CE", "j": ["bell", "bellhop", "hotel"]}, "luggage": {"a": "luggage", "b": "1F9F3", "j": ["luggage", "packing", "travel"]}, "hourglass-done": {"a": "hourglass done", "b": "231B", "j": ["hourglass done", "sand", "timer"]}, "hourglass-not-done": {"a": "hourglass not done", "b": "23F3", "j": ["hourglass", "hourglass not done", "sand", "timer"]}, "watch": {"a": "watch", "b": "231A", "j": ["clock", "watch"]}, "alarm-clock": {"a": "alarm clock", "b": "23F0", "j": ["alarm", "clock"]}, "stopwatch": {"a": "stopwatch", "b": "23F1", "j": ["clock", "stopwatch"]}, "timer-clock": {"a": "timer clock", "b": "23F2", "j": ["clock", "timer"]}, "mantelpiece-clock": {"a": "mantelpiece clock", "b": "1F570", "j": ["clock", "mantelpiece clock"]}, "twelve-oclock": {"a": "twelve o’clock", "b": "1F55B", "j": ["00", "12", "12:00", "clock", "o’clock", "twelve"]}, "twelvethirty": {"a": "twelve-thirty", "b": "1F567", "j": ["12", "12:30", "clock", "thirty", "twelve", "twelve-thirty"]}, "one-oclock": {"a": "one o’clock", "b": "1F550", "j": ["00", "1", "1:00", "clock", "o’clock", "one"]}, "onethirty": {"a": "one-thirty", "b": "1F55C", "j": ["1", "1:30", "clock", "one", "one-thirty", "thirty"]}, "two-oclock": {"a": "two o’clock", "b": "1F551", "j": ["00", "2", "2:00", "clock", "o’clock", "two"]}, "twothirty": {"a": "two-thirty", "b": "1F55D", "j": ["2", "2:30", "clock", "thirty", "two", "two-thirty"]}, "three-oclock": {"a": "three o’clock", "b": "1F552", "j": ["00", "3", "3:00", "clock", "o’clock", "three"]}, "threethirty": {"a": "three-thirty", "b": "1F55E", "j": ["3", "3:30", "clock", "thirty", "three", "three-thirty"]}, "four-oclock": {"a": "four o’clock", "b": "1F553", "j": ["00", "4", "4:00", "clock", "four", "o’clock"]}, "fourthirty": {"a": "four-thirty", "b": "1F55F", "j": ["4", "4:30", "clock", "four", "four-thirty", "thirty"]}, "five-oclock": {"a": "five o’clock", "b": "1F554", "j": ["00", "5", "5:00", "clock", "five", "o’clock"]}, "fivethirty": {"a": "five-thirty", "b": "1F560", "j": ["5", "5:30", "clock", "five", "five-thirty", "thirty"]}, "six-oclock": {"a": "six o’clock", "b": "1F555", "j": ["00", "6", "6:00", "clock", "o’clock", "six"]}, "sixthirty": {"a": "six-thirty", "b": "1F561", "j": ["6", "6:30", "clock", "six", "six-thirty", "thirty"]}, "seven-oclock": {"a": "seven o’clock", "b": "1F556", "j": ["00", "7", "7:00", "clock", "o’clock", "seven"]}, "seventhirty": {"a": "seven-thirty", "b": "1F562", "j": ["7", "7:30", "clock", "seven", "seven-thirty", "thirty"]}, "eight-oclock": {"a": "eight o’clock", "b": "1F557", "j": ["00", "8", "8:00", "clock", "eight", "o’clock"]}, "eightthirty": {"a": "eight-thirty", "b": "1F563", "j": ["8", "8:30", "clock", "eight", "eight-thirty", "thirty"]}, "nine-oclock": {"a": "nine o’clock", "b": "1F558", "j": ["00", "9", "9:00", "clock", "nine", "o’clock"]}, "ninethirty": {"a": "nine-thirty", "b": "1F564", "j": ["9", "9:30", "clock", "nine", "nine-thirty", "thirty"]}, "ten-oclock": {"a": "ten o’clock", "b": "1F559", "j": ["00", "10", "10:00", "clock", "o’clock", "ten"]}, "tenthirty": {"a": "ten-thirty", "b": "1F565", "j": ["10", "10:30", "clock", "ten", "ten-thirty", "thirty"]}, "eleven-oclock": {"a": "eleven o’clock", "b": "1F55A", "j": ["00", "11", "11:00", "clock", "eleven", "o’clock"]}, "eleventhirty": {"a": "eleven-thirty", "b": "1F566", "j": ["11", "11:30", "clock", "eleven", "eleven-thirty", "thirty"]}, "new-moon": {"a": "new moon", "b": "1F311", "j": ["dark", "moon", "new moon"]}, "waxing-crescent-moon": {"a": "waxing crescent moon", "b": "1F312", "j": ["crescent", "moon", "waxing"]}, "first-quarter-moon": {"a": "first quarter moon", "b": "1F313", "j": ["first quarter moon", "moon", "quarter"]}, "waxing-gibbous-moon": {"a": "waxing gibbous moon", "b": "1F314", "j": ["gibbous", "moon", "waxing"]}, "full-moon": {"a": "full moon", "b": "1F315", "j": ["full", "moon"]}, "waning-gibbous-moon": {"a": "waning gibbous moon", "b": "1F316", "j": ["gibbous", "moon", "waning"]}, "last-quarter-moon": {"a": "last quarter moon", "b": "1F317", "j": ["last quarter moon", "moon", "quarter"]}, "waning-crescent-moon": {"a": "waning crescent moon", "b": "1F318", "j": ["crescent", "moon", "waning"]}, "crescent-moon": {"a": "crescent moon", "b": "1F319", "j": ["crescent", "moon"]}, "new-moon-face": {"a": "new moon face", "b": "1F31A", "j": ["face", "moon", "new moon face"]}, "first-quarter-moon-face": {"a": "first quarter moon face", "b": "1F31B", "j": ["face", "first quarter moon face", "moon", "quarter"]}, "last-quarter-moon-face": {"a": "last quarter moon face", "b": "1F31C", "j": ["face", "last quarter moon face", "moon", "quarter"]}, "thermometer": {"a": "thermometer", "b": "1F321", "j": ["thermometer", "weather"]}, "sun": {"a": "sun", "b": "2600", "j": ["bright", "rays", "sun", "sunny"]}, "full-moon-face": {"a": "full moon face", "b": "1F31D", "j": ["bright", "face", "full", "moon"]}, "sun-with-face": {"a": "sun with face", "b": "1F31E", "j": ["bright", "face", "sun", "sun with face"]}, "ringed-planet": {"a": "ringed planet", "b": "1FA90", "j": ["ringed planet", "saturn", "saturnine"]}, "star": {"a": "star", "b": "2B50", "j": ["star"]}, "glowing-star": {"a": "glowing star", "b": "1F31F", "j": ["glittery", "glow", "glowing star", "shining", "sparkle", "star"]}, "shooting-star": {"a": "shooting star", "b": "1F320", "j": ["falling", "shooting", "star"]}, "milky-way": {"a": "milky way", "b": "1F30C", "j": ["milky way", "space"]}, "cloud": {"a": "cloud", "b": "2601", "j": ["cloud", "weather"]}, "sun-behind-cloud": {"a": "sun behind cloud", "b": "26C5", "j": ["cloud", "sun", "sun behind cloud"]}, "cloud-with-lightning-and-rain": {"a": "cloud with lightning and rain", "b": "26C8", "j": ["cloud", "cloud with lightning and rain", "rain", "thunder"]}, "sun-behind-small-cloud": {"a": "sun behind small cloud", "b": "1F324", "j": ["cloud", "sun", "sun behind small cloud"]}, "sun-behind-large-cloud": {"a": "sun behind large cloud", "b": "1F325", "j": ["cloud", "sun", "sun behind large cloud"]}, "sun-behind-rain-cloud": {"a": "sun behind rain cloud", "b": "1F326", "j": ["cloud", "rain", "sun", "sun behind rain cloud"]}, "cloud-with-rain": {"a": "cloud with rain", "b": "1F327", "j": ["cloud", "cloud with rain", "rain"]}, "cloud-with-snow": {"a": "cloud with snow", "b": "1F328", "j": ["cloud", "cloud with snow", "cold", "snow"]}, "cloud-with-lightning": {"a": "cloud with lightning", "b": "1F329", "j": ["cloud", "cloud with lightning", "lightning"]}, "tornado": {"a": "tornado", "b": "1F32A", "j": ["cloud", "tornado", "whirlwind"]}, "fog": {"a": "fog", "b": "1F32B", "j": ["cloud", "fog"]}, "wind-face": {"a": "wind face", "b": "1F32C", "j": ["blow", "cloud", "face", "wind"]}, "cyclone": {"a": "cyclone", "b": "1F300", "j": ["cyclone", "dizzy", "hurricane", "twister", "typhoon"]}, "rainbow": {"a": "rainbow", "b": "1F308", "j": ["rain", "rainbow"]}, "closed-umbrella": {"a": "closed umbrella", "b": "1F302", "j": ["closed umbrella", "clothing", "rain", "umbrella"]}, "umbrella": {"a": "umbrella", "b": "2602", "j": ["clothing", "rain", "umbrella"]}, "umbrella-with-rain-drops": {"a": "umbrella with rain drops", "b": "2614", "j": ["clothing", "drop", "rain", "umbrella", "umbrella with rain drops"]}, "umbrella-on-ground": {"a": "umbrella on ground", "b": "26F1", "j": ["rain", "sun", "umbrella", "umbrella on ground"]}, "high-voltage": {"a": "high voltage", "b": "26A1", "j": ["danger", "electric", "high voltage", "lightning", "voltage", "zap"]}, "snowflake": {"a": "snowflake", "b": "2744", "j": ["cold", "snow", "snowflake"]}, "snowman": {"a": "snowman", "b": "2603", "j": ["cold", "snow", "snowman"]}, "snowman-without-snow": {"a": "snowman without snow", "b": "26C4", "j": ["cold", "snow", "snowman", "snowman without snow"]}, "comet": {"a": "comet", "b": "2604", "j": ["comet", "space"]}, "fire": {"a": "fire", "b": "1F525", "j": ["fire", "flame", "tool"]}, "droplet": {"a": "droplet", "b": "1F4A7", "j": ["cold", "comic", "drop", "droplet", "sweat"]}, "water-wave": {"a": "water wave", "b": "1F30A", "j": ["ocean", "water", "wave"]}, "jackolantern": {"a": "jack-o-lantern", "b": "1F383", "j": ["celebration", "halloween", "jack", "jack-o-lantern", "lantern"]}, "christmas-tree": {"a": "Christmas tree", "b": "1F384", "j": ["celebration", "Christmas", "tree"]}, "fireworks": {"a": "fireworks", "b": "1F386", "j": ["celebration", "fireworks"]}, "sparkler": {"a": "sparkler", "b": "1F387", "j": ["celebration", "fireworks", "sparkle", "sparkler"]}, "firecracker": {"a": "firecracker", "b": "1F9E8", "j": ["dynamite", "explosive", "firecracker", "fireworks"]}, "sparkles": {"a": "sparkles", "b": "2728", "j": ["*", "sparkle", "sparkles", "star"]}, "balloon": {"a": "balloon", "b": "1F388", "j": ["balloon", "celebration"]}, "party-popper": {"a": "party popper", "b": "1F389", "j": ["celebration", "party", "popper", "tada"]}, "confetti-ball": {"a": "confetti ball", "b": "1F38A", "j": ["ball", "celebration", "confetti"]}, "tanabata-tree": {"a": "tanabata tree", "b": "1F38B", "j": ["banner", "celebration", "Japanese", "tanabata tree", "tree"]}, "pine-decoration": {"a": "pine decoration", "b": "1F38D", "j": ["bamboo", "celebration", "Japanese", "pine", "pine decoration"]}, "japanese-dolls": {"a": "Japanese dolls", "b": "1F38E", "j": ["celebration", "doll", "festival", "Japanese", "Japanese dolls"]}, "carp-streamer": {"a": "carp streamer", "b": "1F38F", "j": ["carp", "celebration", "streamer"]}, "wind-chime": {"a": "wind chime", "b": "1F390", "j": ["bell", "celebration", "chime", "wind"]}, "moon-viewing-ceremony": {"a": "moon viewing ceremony", "b": "1F391", "j": ["celebration", "ceremony", "moon", "moon viewing ceremony"]}, "red-envelope": {"a": "red envelope", "b": "1F9E7", "j": ["gift", "good luck", "hóngbāo", "lai see", "money", "red envelope"]}, "ribbon": {"a": "ribbon", "b": "1F380", "j": ["celebration", "ribbon"]}, "wrapped-gift": {"a": "wrapped gift", "b": "1F381", "j": ["box", "celebration", "gift", "present", "wrapped"]}, "reminder-ribbon": {"a": "reminder ribbon", "b": "1F397", "j": ["celebration", "reminder", "ribbon"]}, "admission-tickets": {"a": "admission tickets", "b": "1F39F", "j": ["admission", "admission tickets", "ticket"]}, "ticket": {"a": "ticket", "b": "1F3AB", "j": ["admission", "ticket"]}, "military-medal": {"a": "military medal", "b": "1F396", "j": ["celebration", "medal", "military"]}, "trophy": {"a": "trophy", "b": "1F3C6", "j": ["prize", "trophy"]}, "sports-medal": {"a": "sports medal", "b": "1F3C5", "j": ["medal", "sports medal"]}, "1st-place-medal": {"a": "1st place medal", "b": "1F947", "j": ["1st place medal", "first", "gold", "medal"]}, "2nd-place-medal": {"a": "2nd place medal", "b": "1F948", "j": ["2nd place medal", "medal", "second", "silver"]}, "3rd-place-medal": {"a": "3rd place medal", "b": "1F949", "j": ["3rd place medal", "bronze", "medal", "third"]}, "soccer-ball": {"a": "soccer ball", "b": "26BD", "j": ["ball", "football", "soccer"]}, "baseball": {"a": "baseball", "b": "26BE", "j": ["ball", "baseball"]}, "softball": {"a": "softball", "b": "1F94E", "j": ["ball", "glove", "softball", "underarm"]}, "basketball": {"a": "basketball", "b": "1F3C0", "j": ["ball", "basketball", "hoop"]}, "volleyball": {"a": "volleyball", "b": "1F3D0", "j": ["ball", "game", "volleyball"]}, "american-football": {"a": "american football", "b": "1F3C8", "j": ["american", "ball", "football"]}, "rugby-football": {"a": "rugby football", "b": "1F3C9", "j": ["ball", "football", "rugby"]}, "tennis": {"a": "tennis", "b": "1F3BE", "j": ["ball", "racquet", "tennis"]}, "flying-disc": {"a": "flying disc", "b": "1F94F", "j": ["flying disc", "ultimate"]}, "bowling": {"a": "bowling", "b": "1F3B3", "j": ["ball", "bowling", "game"]}, "cricket-game": {"a": "cricket game", "b": "1F3CF", "j": ["ball", "bat", "cricket game", "game"]}, "field-hockey": {"a": "field hockey", "b": "1F3D1", "j": ["ball", "field", "game", "hockey", "stick"]}, "ice-hockey": {"a": "ice hockey", "b": "1F3D2", "j": ["game", "hockey", "ice", "puck", "stick"]}, "lacrosse": {"a": "lacrosse", "b": "1F94D", "j": ["ball", "goal", "lacrosse", "stick"]}, "ping-pong": {"a": "ping pong", "b": "1F3D3", "j": ["ball", "bat", "game", "paddle", "ping pong", "table tennis"]}, "badminton": {"a": "badminton", "b": "1F3F8", "j": ["badminton", "birdie", "game", "racquet", "shuttlecock"]}, "boxing-glove": {"a": "boxing glove", "b": "1F94A", "j": ["boxing", "glove"]}, "martial-arts-uniform": {"a": "martial arts uniform", "b": "1F94B", "j": ["judo", "karate", "martial arts", "martial arts uniform", "taekwondo", "uniform"]}, "goal-net": {"a": "goal net", "b": "1F945", "j": ["goal", "net"]}, "flag-in-hole": {"a": "flag in hole", "b": "26F3", "j": ["flag in hole", "golf", "hole"]}, "ice-skate": {"a": "ice skate", "b": "26F8", "j": ["ice", "skate"]}, "fishing-pole": {"a": "fishing pole", "b": "1F3A3", "j": ["fish", "fishing pole", "pole"]}, "diving-mask": {"a": "diving mask", "b": "1F93F", "j": ["diving", "diving mask", "scuba", "snorkeling"]}, "running-shirt": {"a": "running shirt", "b": "1F3BD", "j": ["athletics", "running", "sash", "shirt"]}, "skis": {"a": "skis", "b": "1F3BF", "j": ["ski", "skis", "snow"]}, "sled": {"a": "sled", "b": "1F6F7", "j": ["sled", "sledge", "sleigh", "luge", "toboggan"]}, "curling-stone": {"a": "curling stone", "b": "1F94C", "j": ["curling stone", "game", "rock"]}, "bullseye": {"a": "bullseye", "b": "1F3AF", "j": ["bullseye", "dart", "direct hit", "game", "hit", "target"]}, "yoyo": {"a": "yo-yo", "b": "1FA80", "j": ["fluctuate", "toy", "yo-yo"]}, "kite": {"a": "kite", "b": "1FA81", "j": ["fly", "kite", "soar"]}, "pool-8-ball": {"a": "pool 8 ball", "b": "1F3B1", "j": ["8", "ball", "billiard", "eight", "game", "pool 8 ball"]}, "crystal-ball": {"a": "crystal ball", "b": "1F52E", "j": ["ball", "crystal", "fairy tale", "fantasy", "fortune", "tool"]}, "magic-wand": {"a": "magic wand", "b": "1FA84", "j": ["magic", "magic wand", "witch", "wizard"]}, "nazar-amulet": {"a": "nazar amulet", "b": "1F9FF", "j": ["bead", "charm", "evil-eye", "nazar", "nazar amulet", "talisman"]}, "video-game": {"a": "video game", "b": "1F3AE", "j": ["controller", "game", "video game"]}, "joystick": {"a": "joystick", "b": "1F579", "j": ["game", "joystick", "video game"]}, "slot-machine": {"a": "slot machine", "b": "1F3B0", "j": ["game", "slot", "slot machine"]}, "game-die": {"a": "game die", "b": "1F3B2", "j": ["dice", "die", "game"]}, "puzzle-piece": {"a": "puzzle piece", "b": "1F9E9", "j": ["clue", "interlocking", "jigsaw", "piece", "puzzle"]}, "teddy-bear": {"a": "teddy bear", "b": "1F9F8", "j": ["plaything", "plush", "stuffed", "teddy bear", "toy"]}, "piata": {"a": "piñata", "b": "1FA85", "j": ["celebration", "party", "piñata"]}, "nesting-dolls": {"a": "nesting dolls", "b": "1FA86", "j": ["doll", "nesting", "nesting dolls", "russia"]}, "spade-suit": {"a": "spade suit", "b": "2660", "j": ["card", "game", "spade suit"]}, "heart-suit": {"a": "heart suit", "b": "2665", "j": ["card", "game", "heart suit"]}, "diamond-suit": {"a": "diamond suit", "b": "2666", "j": ["card", "diamond suit", "game"]}, "club-suit": {"a": "club suit", "b": "2663", "j": ["card", "club suit", "game"]}, "chess-pawn": {"a": "chess pawn", "b": "265F", "j": ["chess", "chess pawn", "dupe", "expendable"]}, "joker": {"a": "joker", "b": "1F0CF", "j": ["card", "game", "joker", "wildcard"]}, "mahjong-red-dragon": {"a": "mahjong red dragon", "b": "1F004", "j": ["game", "mahjong", "mahjong red dragon", "red"]}, "flower-playing-cards": {"a": "flower playing cards", "b": "1F3B4", "j": ["card", "flower", "flower playing cards", "game", "Japanese", "playing"]}, "performing-arts": {"a": "performing arts", "b": "1F3AD", "j": ["art", "mask", "performing", "performing arts", "theater", "theatre"]}, "framed-picture": {"a": "framed picture", "b": "1F5BC", "j": ["art", "frame", "framed picture", "museum", "painting", "picture"]}, "artist-palette": {"a": "artist palette", "b": "1F3A8", "j": ["art", "artist palette", "museum", "painting", "palette"]}, "thread": {"a": "thread", "b": "1F9F5", "j": ["needle", "sewing", "spool", "string", "thread"]}, "sewing-needle": {"a": "sewing needle", "b": "1FAA1", "j": ["embroidery", "needle", "sewing", "stitches", "sutures", "tailoring"]}, "yarn": {"a": "yarn", "b": "1F9F6", "j": ["ball", "crochet", "knit", "yarn"]}, "knot": {"a": "knot", "b": "1FAA2", "j": ["knot", "rope", "tangled", "tie", "twine", "twist"]}, "glasses": {"a": "glasses", "b": "1F453", "j": ["clothing", "eye", "eyeglasses", "eyewear", "glasses"]}, "sunglasses": {"a": "sunglasses", "b": "1F576", "j": ["dark", "eye", "eyewear", "glasses", "sunglasses"]}, "goggles": {"a": "goggles", "b": "1F97D", "j": ["eye protection", "goggles", "swimming", "welding"]}, "lab-coat": {"a": "lab coat", "b": "1F97C", "j": ["doctor", "experiment", "lab coat", "scientist"]}, "safety-vest": {"a": "safety vest", "b": "1F9BA", "j": ["emergency", "safety", "vest"]}, "necktie": {"a": "necktie", "b": "1F454", "j": ["clothing", "necktie", "tie"]}, "tshirt": {"a": "t-shirt", "b": "1F455", "j": ["clothing", "shirt", "t-shirt", "tshirt"]}, "jeans": {"a": "jeans", "b": "1F456", "j": ["clothing", "jeans", "pants", "trousers"]}, "scarf": {"a": "scarf", "b": "1F9E3", "j": ["neck", "scarf"]}, "gloves": {"a": "gloves", "b": "1F9E4", "j": ["gloves", "hand"]}, "coat": {"a": "coat", "b": "1F9E5", "j": ["coat", "jacket"]}, "socks": {"a": "socks", "b": "1F9E6", "j": ["socks", "stocking"]}, "dress": {"a": "dress", "b": "1F457", "j": ["clothing", "dress"]}, "kimono": {"a": "kimono", "b": "1F458", "j": ["clothing", "kimono"]}, "sari": {"a": "sari", "b": "1F97B", "j": ["clothing", "dress", "sari"]}, "onepiece-swimsuit": {"a": "one-piece swimsuit", "b": "1FA71", "j": ["bathing suit", "one-piece swimsuit"]}, "briefs": {"a": "briefs", "b": "1FA72", "j": ["bathing suit", "briefs", "one-piece", "swimsuit", "underwear"]}, "shorts": {"a": "shorts", "b": "1FA73", "j": ["bathing suit", "pants", "shorts", "underwear"]}, "bikini": {"a": "bikini", "b": "1F459", "j": ["bikini", "clothing", "swim"]}, "womans-clothes": {"a": "woman’s clothes", "b": "1F45A", "j": ["clothing", "woman", "woman’s clothes"]}, "purse": {"a": "purse", "b": "1F45B", "j": ["clothing", "coin", "purse"]}, "handbag": {"a": "handbag", "b": "1F45C", "j": ["bag", "clothing", "handbag", "purse"]}, "clutch-bag": {"a": "clutch bag", "b": "1F45D", "j": ["bag", "clothing", "clutch bag", "pouch"]}, "shopping-bags": {"a": "shopping bags", "b": "1F6CD", "j": ["bag", "hotel", "shopping", "shopping bags"]}, "backpack": {"a": "backpack", "b": "1F392", "j": ["backpack", "bag", "rucksack", "satchel", "school"]}, "thong-sandal": {"a": "thong sandal", "b": "1FA74", "j": ["beach sandals", "sandals", "thong sandal", "thong sandals", "thongs", "zōri"]}, "mans-shoe": {"a": "man’s shoe", "b": "1F45E", "j": ["clothing", "man", "man’s shoe", "shoe"]}, "running-shoe": {"a": "running shoe", "b": "1F45F", "j": ["athletic", "clothing", "running shoe", "shoe", "sneaker"]}, "hiking-boot": {"a": "hiking boot", "b": "1F97E", "j": ["backpacking", "boot", "camping", "hiking"]}, "flat-shoe": {"a": "flat shoe", "b": "1F97F", "j": ["ballet flat", "flat shoe", "slip-on", "slipper"]}, "highheeled-shoe": {"a": "high-heeled shoe", "b": "1F460", "j": ["clothing", "heel", "high-heeled shoe", "shoe", "woman"]}, "womans-sandal": {"a": "woman’s sandal", "b": "1F461", "j": ["clothing", "sandal", "shoe", "woman", "woman’s sandal"]}, "ballet-shoes": {"a": "ballet shoes", "b": "1FA70", "j": ["ballet", "ballet shoes", "dance"]}, "womans-boot": {"a": "woman’s boot", "b": "1F462", "j": ["boot", "clothing", "shoe", "woman", "woman’s boot"]}, "crown": {"a": "crown", "b": "1F451", "j": ["clothing", "crown", "king", "queen"]}, "womans-hat": {"a": "woman’s hat", "b": "1F452", "j": ["clothing", "hat", "woman", "woman’s hat"]}, "top-hat": {"a": "top hat", "b": "1F3A9", "j": ["clothing", "hat", "top", "tophat"]}, "graduation-cap": {"a": "graduation cap", "b": "1F393", "j": ["cap", "celebration", "clothing", "graduation", "hat"]}, "billed-cap": {"a": "billed cap", "b": "1F9E2", "j": ["baseball cap", "billed cap"]}, "military-helmet": {"a": "military helmet", "b": "1FA96", "j": ["army", "helmet", "military", "soldier", "warrior"]}, "rescue-workers-helmet": {"a": "rescue worker’s helmet", "b": "26D1", "j": ["aid", "cross", "face", "hat", "helmet", "rescue worker’s helmet"]}, "prayer-beads": {"a": "prayer beads", "b": "1F4FF", "j": ["beads", "clothing", "necklace", "prayer", "religion"]}, "lipstick": {"a": "lipstick", "b": "1F484", "j": ["cosmetics", "lipstick", "makeup"]}, "ring": {"a": "ring", "b": "1F48D", "j": ["diamond", "ring"]}, "gem-stone": {"a": "gem stone", "b": "1F48E", "j": ["diamond", "gem", "gem stone", "jewel"]}, "muted-speaker": {"a": "muted speaker", "b": "1F507", "j": ["mute", "muted speaker", "quiet", "silent", "speaker"]}, "speaker-low-volume": {"a": "speaker low volume", "b": "1F508", "j": ["soft", "speaker low volume"]}, "speaker-medium-volume": {"a": "speaker medium volume", "b": "1F509", "j": ["medium", "speaker medium volume"]}, "speaker-high-volume": {"a": "speaker high volume", "b": "1F50A", "j": ["loud", "speaker high volume"]}, "loudspeaker": {"a": "loudspeaker", "b": "1F4E2", "j": ["loud", "loudspeaker", "public address"]}, "megaphone": {"a": "megaphone", "b": "1F4E3", "j": ["cheering", "megaphone"]}, "postal-horn": {"a": "postal horn", "b": "1F4EF", "j": ["horn", "post", "postal"]}, "bell": {"a": "bell", "b": "1F514", "j": ["bell"]}, "bell-with-slash": {"a": "bell with slash", "b": "1F515", "j": ["bell", "bell with slash", "forbidden", "mute", "quiet", "silent"]}, "musical-score": {"a": "musical score", "b": "1F3BC", "j": ["music", "musical score", "score"]}, "musical-note": {"a": "musical note", "b": "1F3B5", "j": ["music", "musical note", "note"]}, "musical-notes": {"a": "musical notes", "b": "1F3B6", "j": ["music", "musical notes", "note", "notes"]}, "studio-microphone": {"a": "studio microphone", "b": "1F399", "j": ["mic", "microphone", "music", "studio"]}, "level-slider": {"a": "level slider", "b": "1F39A", "j": ["level", "music", "slider"]}, "control-knobs": {"a": "control knobs", "b": "1F39B", "j": ["control", "knobs", "music"]}, "microphone": {"a": "microphone", "b": "1F3A4", "j": ["karaoke", "mic", "microphone"]}, "headphone": {"a": "headphone", "b": "1F3A7", "j": ["earbud", "headphone"]}, "radio": {"a": "radio", "b": "1F4FB", "j": ["radio", "video"]}, "saxophone": {"a": "saxophone", "b": "1F3B7", "j": ["instrument", "music", "sax", "saxophone"]}, "accordion": {"a": "accordion", "b": "1FA97", "j": ["accordian", "accordion", "concertina", "squeeze box"]}, "guitar": {"a": "guitar", "b": "1F3B8", "j": ["guitar", "instrument", "music"]}, "musical-keyboard": {"a": "musical keyboard", "b": "1F3B9", "j": ["instrument", "keyboard", "music", "musical keyboard", "piano"]}, "trumpet": {"a": "trumpet", "b": "1F3BA", "j": ["instrument", "music", "trumpet"]}, "violin": {"a": "violin", "b": "1F3BB", "j": ["instrument", "music", "violin"]}, "banjo": {"a": "banjo", "b": "1FA95", "j": ["banjo", "music", "stringed"]}, "drum": {"a": "drum", "b": "1F941", "j": ["drum", "drumsticks", "music"]}, "long-drum": {"a": "long drum", "b": "1FA98", "j": ["beat", "conga", "drum", "long drum", "rhythm"]}, "mobile-phone": {"a": "mobile phone", "b": "1F4F1", "j": ["cell", "mobile", "phone", "telephone"]}, "mobile-phone-with-arrow": {"a": "mobile phone with arrow", "b": "1F4F2", "j": ["arrow", "cell", "mobile", "mobile phone with arrow", "phone", "receive"]}, "telephone": {"a": "telephone", "b": "260E", "j": ["phone", "telephone"]}, "telephone-receiver": {"a": "telephone receiver", "b": "1F4DE", "j": ["phone", "receiver", "telephone"]}, "pager": {"a": "pager", "b": "1F4DF", "j": ["pager"]}, "fax-machine": {"a": "fax machine", "b": "1F4E0", "j": ["fax", "fax machine"]}, "battery": {"a": "battery", "b": "1F50B", "j": ["battery"]}, "electric-plug": {"a": "electric plug", "b": "1F50C", "j": ["electric", "electricity", "plug"]}, "laptop": {"a": "laptop", "b": "1F4BB", "j": ["computer", "laptop", "pc", "personal"]}, "desktop-computer": {"a": "desktop computer", "b": "1F5A5", "j": ["computer", "desktop"]}, "printer": {"a": "printer", "b": "1F5A8", "j": ["computer", "printer"]}, "keyboard": {"a": "keyboard", "b": "2328", "j": ["computer", "keyboard"]}, "computer-mouse": {"a": "computer mouse", "b": "1F5B1", "j": ["computer", "computer mouse"]}, "trackball": {"a": "trackball", "b": "1F5B2", "j": ["computer", "trackball"]}, "computer-disk": {"a": "computer disk", "b": "1F4BD", "j": ["computer", "disk", "minidisk", "optical"]}, "floppy-disk": {"a": "floppy disk", "b": "1F4BE", "j": ["computer", "disk", "floppy"]}, "optical-disk": {"a": "optical disk", "b": "1F4BF", "j": ["cd", "computer", "disk", "optical"]}, "dvd": {"a": "dvd", "b": "1F4C0", "j": ["blu-ray", "computer", "disk", "dvd", "optical"]}, "abacus": {"a": "abacus", "b": "1F9EE", "j": ["abacus", "calculation"]}, "movie-camera": {"a": "movie camera", "b": "1F3A5", "j": ["camera", "cinema", "movie"]}, "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"]}, "clapper-board": {"a": "clapper board", "b": "1F3AC", "j": ["clapper", "clapper board", "movie"]}, "television": {"a": "television", "b": "1F4FA", "j": ["television", "tv", "video"]}, "camera": {"a": "camera", "b": "1F4F7", "j": ["camera", "video"]}, "camera-with-flash": {"a": "camera with flash", "b": "1F4F8", "j": ["camera", "camera with flash", "flash", "video"]}, "video-camera": {"a": "video camera", "b": "1F4F9", "j": ["camera", "video"]}, "videocassette": {"a": "videocassette", "b": "1F4FC", "j": ["tape", "vhs", "video", "videocassette"]}, "magnifying-glass-tilted-left": {"a": "magnifying glass tilted left", "b": "1F50D", "j": ["glass", "magnifying", "magnifying glass tilted left", "search", "tool"]}, "magnifying-glass-tilted-right": {"a": "magnifying glass tilted right", "b": "1F50E", "j": ["glass", "magnifying", "magnifying glass tilted right", "search", "tool"]}, "candle": {"a": "candle", "b": "1F56F", "j": ["candle", "light"]}, "light-bulb": {"a": "light bulb", "b": "1F4A1", "j": ["bulb", "comic", "electric", "idea", "light"]}, "flashlight": {"a": "flashlight", "b": "1F526", "j": ["electric", "flashlight", "light", "tool", "torch"]}, "red-paper-lantern": {"a": "red paper lantern", "b": "1F3EE", "j": ["bar", "lantern", "light", "red", "red paper lantern"]}, "diya-lamp": {"a": "diya lamp", "b": "1FA94", "j": ["diya", "lamp", "oil"]}, "notebook-with-decorative-cover": {"a": "notebook with decorative cover", "b": "1F4D4", "j": ["book", "cover", "decorated", "notebook", "notebook with decorative cover"]}, "closed-book": {"a": "closed book", "b": "1F4D5", "j": ["book", "closed"]}, "open-book": {"a": "open book", "b": "1F4D6", "j": ["book", "open"]}, "green-book": {"a": "green book", "b": "1F4D7", "j": ["book", "green"]}, "blue-book": {"a": "blue book", "b": "1F4D8", "j": ["blue", "book"]}, "orange-book": {"a": "orange book", "b": "1F4D9", "j": ["book", "orange"]}, "books": {"a": "books", "b": "1F4DA", "j": ["book", "books"]}, "notebook": {"a": "notebook", "b": "1F4D3", "j": ["notebook"]}, "ledger": {"a": "ledger", "b": "1F4D2", "j": ["ledger", "notebook"]}, "page-with-curl": {"a": "page with curl", "b": "1F4C3", "j": ["curl", "document", "page", "page with curl"]}, "scroll": {"a": "scroll", "b": "1F4DC", "j": ["paper", "scroll"]}, "page-facing-up": {"a": "page facing up", "b": "1F4C4", "j": ["document", "page", "page facing up"]}, "newspaper": {"a": "newspaper", "b": "1F4F0", "j": ["news", "newspaper", "paper"]}, "rolledup-newspaper": {"a": "rolled-up newspaper", "b": "1F5DE", "j": ["news", "newspaper", "paper", "rolled", "rolled-up newspaper"]}, "bookmark-tabs": {"a": "bookmark tabs", "b": "1F4D1", "j": ["bookmark", "mark", "marker", "tabs"]}, "bookmark": {"a": "bookmark", "b": "1F516", "j": ["bookmark", "mark"]}, "label": {"a": "label", "b": "1F3F7", "j": ["label"]}, "money-bag": {"a": "money bag", "b": "1F4B0", "j": ["bag", "dollar", "money", "moneybag"]}, "coin": {"a": "coin", "b": "1FA99", "j": ["coin", "gold", "metal", "money", "silver", "treasure"]}, "yen-banknote": {"a": "yen banknote", "b": "1F4B4", "j": ["banknote", "bill", "currency", "money", "note", "yen"]}, "dollar-banknote": {"a": "dollar banknote", "b": "1F4B5", "j": ["banknote", "bill", "currency", "dollar", "money", "note"]}, "euro-banknote": {"a": "euro banknote", "b": "1F4B6", "j": ["banknote", "bill", "currency", "euro", "money", "note"]}, "pound-banknote": {"a": "pound banknote", "b": "1F4B7", "j": ["banknote", "bill", "currency", "money", "note", "pound"]}, "money-with-wings": {"a": "money with wings", "b": "1F4B8", "j": ["banknote", "bill", "fly", "money", "money with wings", "wings"]}, "credit-card": {"a": "credit card", "b": "1F4B3", "j": ["card", "credit", "money"]}, "receipt": {"a": "receipt", "b": "1F9FE", "j": ["accounting", "bookkeeping", "evidence", "proof", "receipt"]}, "chart-increasing-with-yen": {"a": "chart increasing with yen", "b": "1F4B9", "j": ["chart", "chart increasing with yen", "graph", "growth", "money", "yen"]}, "envelope": {"a": "envelope", "b": "2709", "j": ["email", "envelope", "letter"]}, "email": {"a": "e-mail", "b": "1F4E7", "j": ["e-mail", "email", "letter", "mail"]}, "incoming-envelope": {"a": "incoming envelope", "b": "1F4E8", "j": ["e-mail", "email", "envelope", "incoming", "letter", "receive"]}, "envelope-with-arrow": {"a": "envelope with arrow", "b": "1F4E9", "j": ["arrow", "e-mail", "email", "envelope", "envelope with arrow", "outgoing"]}, "outbox-tray": {"a": "outbox tray", "b": "1F4E4", "j": ["box", "letter", "mail", "outbox", "sent", "tray"]}, "inbox-tray": {"a": "inbox tray", "b": "1F4E5", "j": ["box", "inbox", "letter", "mail", "receive", "tray"]}, "package": {"a": "package", "b": "1F4E6", "j": ["box", "package", "parcel"]}, "closed-mailbox-with-raised-flag": {"a": "closed mailbox with raised flag", "b": "1F4EB", "j": ["closed", "closed mailbox with raised flag", "mail", "mailbox", "postbox"]}, "closed-mailbox-with-lowered-flag": {"a": "closed mailbox with lowered flag", "b": "1F4EA", "j": ["closed", "closed mailbox with lowered flag", "lowered", "mail", "mailbox", "postbox"]}, "open-mailbox-with-raised-flag": {"a": "open mailbox with raised flag", "b": "1F4EC", "j": ["mail", "mailbox", "open", "open mailbox with raised flag", "postbox"]}, "open-mailbox-with-lowered-flag": {"a": "open mailbox with lowered flag", "b": "1F4ED", "j": ["lowered", "mail", "mailbox", "open", "open mailbox with lowered flag", "postbox"]}, "postbox": {"a": "postbox", "b": "1F4EE", "j": ["mail", "mailbox", "postbox"]}, "ballot-box-with-ballot": {"a": "ballot box with ballot", "b": "1F5F3", "j": ["ballot", "ballot box with ballot", "box"]}, "pencil": {"a": "pencil", "b": "270F", "j": ["pencil"]}, "black-nib": {"a": "black nib", "b": "2712", "j": ["black nib", "nib", "pen"]}, "fountain-pen": {"a": "fountain pen", "b": "1F58B", "j": ["fountain", "pen"]}, "pen": {"a": "pen", "b": "1F58A", "j": ["ballpoint", "pen"]}, "paintbrush": {"a": "paintbrush", "b": "1F58C", "j": ["paintbrush", "painting"]}, "crayon": {"a": "crayon", "b": "1F58D", "j": ["crayon"]}, "memo": {"a": "memo", "b": "1F4DD", "j": ["memo", "pencil"]}, "briefcase": {"a": "briefcase", "b": "1F4BC", "j": ["briefcase"]}, "file-folder": {"a": "file folder", "b": "1F4C1", "j": ["file", "folder"]}, "open-file-folder": {"a": "open file folder", "b": "1F4C2", "j": ["file", "folder", "open"]}, "card-index-dividers": {"a": "card index dividers", "b": "1F5C2", "j": ["card", "dividers", "index"]}, "calendar": {"a": "calendar", "b": "1F4C5", "j": ["calendar", "date"]}, "tearoff-calendar": {"a": "tear-off calendar", "b": "1F4C6", "j": ["calendar", "tear-off calendar"]}, "spiral-notepad": {"a": "spiral notepad", "b": "1F5D2", "j": ["note", "pad", "spiral", "spiral notepad"]}, "spiral-calendar": {"a": "spiral calendar", "b": "1F5D3", "j": ["calendar", "pad", "spiral"]}, "card-index": {"a": "card index", "b": "1F4C7", "j": ["card", "index", "rolodex"]}, "chart-increasing": {"a": "chart increasing", "b": "1F4C8", "j": ["chart", "chart increasing", "graph", "growth", "trend", "upward"]}, "chart-decreasing": {"a": "chart decreasing", "b": "1F4C9", "j": ["chart", "chart decreasing", "down", "graph", "trend"]}, "bar-chart": {"a": "bar chart", "b": "1F4CA", "j": ["bar", "chart", "graph"]}, "clipboard": {"a": "clipboard", "b": "1F4CB", "j": ["clipboard"]}, "pushpin": {"a": "pushpin", "b": "1F4CC", "j": ["pin", "pushpin"]}, "round-pushpin": {"a": "round pushpin", "b": "1F4CD", "j": ["pin", "pushpin", "round pushpin"]}, "paperclip": {"a": "paperclip", "b": "1F4CE", "j": ["paperclip"]}, "linked-paperclips": {"a": "linked paperclips", "b": "1F587", "j": ["link", "linked paperclips", "paperclip"]}, "straight-ruler": {"a": "straight ruler", "b": "1F4CF", "j": ["ruler", "straight edge", "straight ruler"]}, "triangular-ruler": {"a": "triangular ruler", "b": "1F4D0", "j": ["ruler", "set", "triangle", "triangular ruler"]}, "scissors": {"a": "scissors", "b": "2702", "j": ["cutting", "scissors", "tool"]}, "card-file-box": {"a": "card file box", "b": "1F5C3", "j": ["box", "card", "file"]}, "file-cabinet": {"a": "file cabinet", "b": "1F5C4", "j": ["cabinet", "file", "filing"]}, "wastebasket": {"a": "wastebasket", "b": "1F5D1", "j": ["wastebasket"]}, "locked": {"a": "locked", "b": "1F512", "j": ["closed", "locked"]}, "unlocked": {"a": "unlocked", "b": "1F513", "j": ["lock", "open", "unlock", "unlocked"]}, "locked-with-pen": {"a": "locked with pen", "b": "1F50F", "j": ["ink", "lock", "locked with pen", "nib", "pen", "privacy"]}, "locked-with-key": {"a": "locked with key", "b": "1F510", "j": ["closed", "key", "lock", "locked with key", "secure"]}, "key": {"a": "key", "b": "1F511", "j": ["key", "lock", "password"]}, "old-key": {"a": "old key", "b": "1F5DD", "j": ["clue", "key", "lock", "old"]}, "hammer": {"a": "hammer", "b": "1F528", "j": ["hammer", "tool"]}, "axe": {"a": "axe", "b": "1FA93", "j": ["axe", "chop", "hatchet", "split", "wood"]}, "pick": {"a": "pick", "b": "26CF", "j": ["mining", "pick", "tool"]}, "hammer-and-pick": {"a": "hammer and pick", "b": "2692", "j": ["hammer", "hammer and pick", "pick", "tool"]}, "hammer-and-wrench": {"a": "hammer and wrench", "b": "1F6E0", "j": ["hammer", "hammer and wrench", "spanner", "tool", "wrench"]}, "dagger": {"a": "dagger", "b": "1F5E1", "j": ["dagger", "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"]}, "boomerang": {"a": "boomerang", "b": "1FA83", "j": ["australia", "boomerang", "rebound", "repercussion"]}, "bow-and-arrow": {"a": "bow and arrow", "b": "1F3F9", "j": ["archer", "arrow", "bow", "bow and arrow", "Sagittarius", "zodiac"]}, "shield": {"a": "shield", "b": "1F6E1", "j": ["shield", "weapon"]}, "carpentry-saw": {"a": "carpentry saw", "b": "1FA9A", "j": ["carpenter", "carpentry saw", "lumber", "saw", "tool"]}, "wrench": {"a": "wrench", "b": "1F527", "j": ["spanner", "tool", "wrench"]}, "screwdriver": {"a": "screwdriver", "b": "1FA9B", "j": ["screw", "screwdriver", "tool"]}, "nut-and-bolt": {"a": "nut and bolt", "b": "1F529", "j": ["bolt", "nut", "nut and bolt", "tool"]}, "gear": {"a": "gear", "b": "2699", "j": ["cog", "cogwheel", "gear", "tool"]}, "clamp": {"a": "clamp", "b": "1F5DC", "j": ["clamp", "compress", "tool", "vice"]}, "balance-scale": {"a": "balance scale", "b": "2696", "j": ["balance", "justice", "Libra", "scale", "zodiac"]}, "white-cane": {"a": "white cane", "b": "1F9AF", "j": ["accessibility", "blind", "white cane"]}, "link": {"a": "link", "b": "1F517", "j": ["link"]}, "chains": {"a": "chains", "b": "26D3", "j": ["chain", "chains"]}, "hook": {"a": "hook", "b": "1FA9D", "j": ["catch", "crook", "curve", "ensnare", "hook", "selling point"]}, "toolbox": {"a": "toolbox", "b": "1F9F0", "j": ["chest", "mechanic", "tool", "toolbox"]}, "magnet": {"a": "magnet", "b": "1F9F2", "j": ["attraction", "horseshoe", "magnet", "magnetic"]}, "ladder": {"a": "ladder", "b": "1FA9C", "j": ["climb", "ladder", "rung", "step"]}, "alembic": {"a": "alembic", "b": "2697", "j": ["alembic", "chemistry", "tool"]}, "test-tube": {"a": "test tube", "b": "1F9EA", "j": ["chemist", "chemistry", "experiment", "lab", "science", "test tube"]}, "petri-dish": {"a": "petri dish", "b": "1F9EB", "j": ["bacteria", "biologist", "biology", "culture", "lab", "petri dish"]}, "dna": {"a": "dna", "b": "1F9EC", "j": ["biologist", "dna", "evolution", "gene", "genetics", "life"]}, "microscope": {"a": "microscope", "b": "1F52C", "j": ["microscope", "science", "tool"]}, "telescope": {"a": "telescope", "b": "1F52D", "j": ["science", "telescope", "tool"]}, "satellite-antenna": {"a": "satellite antenna", "b": "1F4E1", "j": ["antenna", "dish", "satellite"]}, "syringe": {"a": "syringe", "b": "1F489", "j": ["medicine", "needle", "shot", "sick", "syringe"]}, "drop-of-blood": {"a": "drop of blood", "b": "1FA78", "j": ["bleed", "blood donation", "drop of blood", "injury", "medicine", "menstruation"]}, "pill": {"a": "pill", "b": "1F48A", "j": ["doctor", "medicine", "pill", "sick"]}, "adhesive-bandage": {"a": "adhesive bandage", "b": "1FA79", "j": ["adhesive bandage", "bandage"]}, "stethoscope": {"a": "stethoscope", "b": "1FA7A", "j": ["doctor", "heart", "medicine", "stethoscope"]}, "door": {"a": "door", "b": "1F6AA", "j": ["door"]}, "elevator": {"a": "elevator", "b": "1F6D7", "j": ["accessibility", "elevator", "hoist", "lift"]}, "mirror": {"a": "mirror", "b": "1FA9E", "j": ["mirror", "reflection", "reflector", "speculum"]}, "window": {"a": "window", "b": "1FA9F", "j": ["frame", "fresh air", "opening", "transparent", "view", "window"]}, "bed": {"a": "bed", "b": "1F6CF", "j": ["bed", "hotel", "sleep"]}, "couch-and-lamp": {"a": "couch and lamp", "b": "1F6CB", "j": ["couch", "couch and lamp", "hotel", "lamp"]}, "chair": {"a": "chair", "b": "1FA91", "j": ["chair", "seat", "sit"]}, "toilet": {"a": "toilet", "b": "1F6BD", "j": ["toilet"]}, "plunger": {"a": "plunger", "b": "1FAA0", "j": ["force cup", "plumber", "plunger", "suction", "toilet"]}, "shower": {"a": "shower", "b": "1F6BF", "j": ["shower", "water"]}, "bathtub": {"a": "bathtub", "b": "1F6C1", "j": ["bath", "bathtub"]}, "mouse-trap": {"a": "mouse trap", "b": "1FAA4", "j": ["bait", "mouse trap", "mousetrap", "snare", "trap"]}, "razor": {"a": "razor", "b": "1FA92", "j": ["razor", "sharp", "shave"]}, "lotion-bottle": {"a": "lotion bottle", "b": "1F9F4", "j": ["lotion", "lotion bottle", "moisturizer", "shampoo", "sunscreen"]}, "safety-pin": {"a": "safety pin", "b": "1F9F7", "j": ["diaper", "punk rock", "safety pin"]}, "broom": {"a": "broom", "b": "1F9F9", "j": ["broom", "cleaning", "sweeping", "witch"]}, "basket": {"a": "basket", "b": "1F9FA", "j": ["basket", "farming", "laundry", "picnic"]}, "roll-of-paper": {"a": "roll of paper", "b": "1F9FB", "j": ["paper towels", "roll of paper", "toilet paper"]}, "bucket": {"a": "bucket", "b": "1FAA3", "j": ["bucket", "cask", "pail", "vat"]}, "soap": {"a": "soap", "b": "1F9FC", "j": ["bar", "bathing", "cleaning", "lather", "soap", "soapdish"]}, "toothbrush": {"a": "toothbrush", "b": "1FAA5", "j": ["bathroom", "brush", "clean", "dental", "hygiene", "teeth", "toothbrush"]}, "sponge": {"a": "sponge", "b": "1F9FD", "j": ["absorbing", "cleaning", "porous", "sponge"]}, "fire-extinguisher": {"a": "fire extinguisher", "b": "1F9EF", "j": ["extinguish", "fire", "fire extinguisher", "quench"]}, "shopping-cart": {"a": "shopping cart", "b": "1F6D2", "j": ["cart", "shopping", "trolley"]}, "cigarette": {"a": "cigarette", "b": "1F6AC", "j": ["cigarette", "smoking"]}, "coffin": {"a": "coffin", "b": "26B0", "j": ["coffin", "death"]}, "headstone": {"a": "headstone", "b": "1FAA6", "j": ["cemetery", "grave", "graveyard", "headstone", "tombstone"]}, "funeral-urn": {"a": "funeral urn", "b": "26B1", "j": ["ashes", "death", "funeral", "urn"]}, "moai": {"a": "moai", "b": "1F5FF", "j": ["face", "moai", "moyai", "statue"]}, "placard": {"a": "placard", "b": "1FAA7", "j": ["demonstration", "picket", "placard", "protest", "sign"]}, "atm-sign": {"a": "ATM sign", "b": "1F3E7", "j": ["atm", "ATM sign", "automated", "bank", "teller"]}, "litter-in-bin-sign": {"a": "litter in bin sign", "b": "1F6AE", "j": ["litter", "litter bin", "litter in bin sign"]}, "potable-water": {"a": "potable water", "b": "1F6B0", "j": ["drinking", "potable", "water"]}, "wheelchair-symbol": {"a": "wheelchair symbol", "b": "267F", "j": ["access", "wheelchair symbol"]}, "mens-room": {"a": "men’s room", "b": "1F6B9", "j": ["lavatory", "man", "men’s room", "restroom", "wc"]}, "womens-room": {"a": "women’s room", "b": "1F6BA", "j": ["lavatory", "restroom", "wc", "woman", "women’s room"]}, "restroom": {"a": "restroom", "b": "1F6BB", "j": ["lavatory", "restroom", "WC"]}, "baby-symbol": {"a": "baby symbol", "b": "1F6BC", "j": ["baby", "baby symbol", "changing"]}, "water-closet": {"a": "water closet", "b": "1F6BE", "j": ["closet", "lavatory", "restroom", "water", "wc"]}, "passport-control": {"a": "passport control", "b": "1F6C2", "j": ["control", "passport"]}, "customs": {"a": "customs", "b": "1F6C3", "j": ["customs"]}, "baggage-claim": {"a": "baggage claim", "b": "1F6C4", "j": ["baggage", "claim"]}, "left-luggage": {"a": "left luggage", "b": "1F6C5", "j": ["baggage", "left luggage", "locker", "luggage"]}, "warning": {"a": "warning", "b": "26A0", "j": ["warning"]}, "children-crossing": {"a": "children crossing", "b": "1F6B8", "j": ["child", "children crossing", "crossing", "pedestrian", "traffic"]}, "no-entry": {"a": "no entry", "b": "26D4", "j": ["entry", "forbidden", "no", "not", "prohibited", "traffic"]}, "prohibited": {"a": "prohibited", "b": "1F6AB", "j": ["entry", "forbidden", "no", "not", "prohibited"]}, "no-bicycles": {"a": "no bicycles", "b": "1F6B3", "j": ["bicycle", "bike", "forbidden", "no", "no bicycles", "prohibited"]}, "no-smoking": {"a": "no smoking", "b": "1F6AD", "j": ["forbidden", "no", "not", "prohibited", "smoking"]}, "no-littering": {"a": "no littering", "b": "1F6AF", "j": ["forbidden", "litter", "no", "no littering", "not", "prohibited"]}, "nonpotable-water": {"a": "non-potable water", "b": "1F6B1", "j": ["non-drinking", "non-potable", "water"]}, "no-pedestrians": {"a": "no pedestrians", "b": "1F6B7", "j": ["forbidden", "no", "no pedestrians", "not", "pedestrian", "prohibited"]}, "no-mobile-phones": {"a": "no mobile phones", "b": "1F4F5", "j": ["cell", "forbidden", "mobile", "no", "no mobile phones", "phone"]}, "no-one-under-eighteen": {"a": "no one under eighteen", "b": "1F51E", "j": ["18", "age restriction", "eighteen", "no one under eighteen", "prohibited", "underage"]}, "radioactive": {"a": "radioactive", "b": "2622", "j": ["radioactive", "sign"]}, "biohazard": {"a": "biohazard", "b": "2623", "j": ["biohazard", "sign"]}, "up-arrow": {"a": "up arrow", "b": "2B06", "j": ["arrow", "cardinal", "direction", "north", "up arrow"]}, "upright-arrow": {"a": "up-right arrow", "b": "2197", "j": ["arrow", "direction", "intercardinal", "northeast", "up-right arrow"]}, "right-arrow": {"a": "right arrow", "b": "27A1", "j": ["arrow", "cardinal", "direction", "east", "right arrow"]}, "downright-arrow": {"a": "down-right arrow", "b": "2198", "j": ["arrow", "direction", "down-right arrow", "intercardinal", "southeast"]}, "down-arrow": {"a": "down arrow", "b": "2B07", "j": ["arrow", "cardinal", "direction", "down", "south"]}, "downleft-arrow": {"a": "down-left arrow", "b": "2199", "j": ["arrow", "direction", "down-left arrow", "intercardinal", "southwest"]}, "left-arrow": {"a": "left arrow", "b": "2B05", "j": ["arrow", "cardinal", "direction", "left arrow", "west"]}, "upleft-arrow": {"a": "up-left arrow", "b": "2196", "j": ["arrow", "direction", "intercardinal", "northwest", "up-left arrow"]}, "updown-arrow": {"a": "up-down arrow", "b": "2195", "j": ["arrow", "up-down arrow"]}, "leftright-arrow": {"a": "left-right arrow", "b": "2194", "j": ["arrow", "left-right arrow"]}, "right-arrow-curving-left": {"a": "right arrow curving left", "b": "21A9", "j": ["arrow", "right arrow curving left"]}, "left-arrow-curving-right": {"a": "left arrow curving right", "b": "21AA", "j": ["arrow", "left arrow curving right"]}, "right-arrow-curving-up": {"a": "right arrow curving up", "b": "2934", "j": ["arrow", "right arrow curving up"]}, "right-arrow-curving-down": {"a": "right arrow curving down", "b": "2935", "j": ["arrow", "down", "right arrow curving down"]}, "clockwise-vertical-arrows": {"a": "clockwise vertical arrows", "b": "1F503", "j": ["arrow", "clockwise", "clockwise vertical arrows", "reload"]}, "counterclockwise-arrows-button": {"a": "counterclockwise arrows button", "b": "1F504", "j": ["anticlockwise", "arrow", "counterclockwise", "counterclockwise arrows button", "withershins"]}, "back-arrow": {"a": "BACK arrow", "b": "1F519", "j": ["arrow", "back", "BACK arrow"]}, "end-arrow": {"a": "END arrow", "b": "1F51A", "j": ["arrow", "end", "END arrow"]}, "on-arrow": {"a": "ON! arrow", "b": "1F51B", "j": ["arrow", "mark", "on", "ON! arrow"]}, "soon-arrow": {"a": "SOON arrow", "b": "1F51C", "j": ["arrow", "soon", "SOON arrow"]}, "top-arrow": {"a": "TOP arrow", "b": "1F51D", "j": ["arrow", "top", "TOP arrow", "up"]}, "place-of-worship": {"a": "place of worship", "b": "1F6D0", "j": ["place of worship", "religion", "worship"]}, "atom-symbol": {"a": "atom symbol", "b": "269B", "j": ["atheist", "atom", "atom symbol"]}, "om": {"a": "om", "b": "1F549", "j": ["Hindu", "om", "religion"]}, "star-of-david": {"a": "star of David", "b": "2721", "j": ["David", "Jew", "Jewish", "religion", "star", "star of David"]}, "wheel-of-dharma": {"a": "wheel of dharma", "b": "2638", "j": ["Buddhist", "dharma", "religion", "wheel", "wheel of dharma"]}, "yin-yang": {"a": "yin yang", "b": "262F", "j": ["religion", "tao", "taoist", "yang", "yin"]}, "latin-cross": {"a": "latin cross", "b": "271D", "j": ["Christian", "cross", "latin cross", "religion"]}, "orthodox-cross": {"a": "orthodox cross", "b": "2626", "j": ["Christian", "cross", "orthodox cross", "religion"]}, "star-and-crescent": {"a": "star and crescent", "b": "262A", "j": ["islam", "Muslim", "religion", "star and crescent"]}, "peace-symbol": {"a": "peace symbol", "b": "262E", "j": ["peace", "peace symbol"]}, "menorah": {"a": "menorah", "b": "1F54E", "j": ["candelabrum", "candlestick", "menorah", "religion"]}, "dotted-sixpointed-star": {"a": "dotted six-pointed star", "b": "1F52F", "j": ["dotted six-pointed star", "fortune", "star"]}, "aries": {"a": "Aries", "b": "2648", "j": ["Aries", "ram", "zodiac"]}, "taurus": {"a": "Taurus", "b": "2649", "j": ["bull", "ox", "Taurus", "zodiac"]}, "gemini": {"a": "Gemini", "b": "264A", "j": ["Gemini", "twins", "zodiac"]}, "cancer": {"a": "Cancer", "b": "264B", "j": ["Cancer", "crab", "zodiac"]}, "leo": {"a": "Leo", "b": "264C", "j": ["Leo", "lion", "zodiac"]}, "virgo": {"a": "Virgo", "b": "264D", "j": ["Virgo", "zodiac"]}, "libra": {"a": "Libra", "b": "264E", "j": ["balance", "justice", "Libra", "scales", "zodiac"]}, "scorpio": {"a": "Scorpio", "b": "264F", "j": ["Scorpio", "scorpion", "scorpius", "zodiac"]}, "sagittarius": {"a": "Sagittarius", "b": "2650", "j": ["archer", "Sagittarius", "zodiac"]}, "capricorn": {"a": "Capricorn", "b": "2651", "j": ["Capricorn", "goat", "zodiac"]}, "aquarius": {"a": "Aquarius", "b": "2652", "j": ["Aquarius", "bearer", "water", "zodiac"]}, "pisces": {"a": "Pisces", "b": "2653", "j": ["fish", "Pisces", "zodiac"]}, "ophiuchus": {"a": "Ophiuchus", "b": "26CE", "j": ["bearer", "Ophiuchus", "serpent", "snake", "zodiac"]}, "shuffle-tracks-button": {"a": "shuffle tracks button", "b": "1F500", "j": ["arrow", "crossed", "shuffle tracks button"]}, "repeat-button": {"a": "repeat button", "b": "1F501", "j": ["arrow", "clockwise", "repeat", "repeat button"]}, "repeat-single-button": {"a": "repeat single button", "b": "1F502", "j": ["arrow", "clockwise", "once", "repeat single button"]}, "play-button": {"a": "play button", "b": "25B6", "j": ["arrow", "play", "play button", "right", "triangle"]}, "fastforward-button": {"a": "fast-forward button", "b": "23E9", "j": ["arrow", "double", "fast", "fast-forward button", "forward"]}, "next-track-button": {"a": "next track button", "b": "23ED", "j": ["arrow", "next scene", "next track", "next track button", "triangle"]}, "play-or-pause-button": {"a": "play or pause button", "b": "23EF", "j": ["arrow", "pause", "play", "play or pause button", "right", "triangle"]}, "reverse-button": {"a": "reverse button", "b": "25C0", "j": ["arrow", "left", "reverse", "reverse button", "triangle"]}, "fast-reverse-button": {"a": "fast reverse button", "b": "23EA", "j": ["arrow", "double", "fast reverse button", "rewind"]}, "last-track-button": {"a": "last track button", "b": "23EE", "j": ["arrow", "last track button", "previous scene", "previous track", "triangle"]}, "upwards-button": {"a": "upwards button", "b": "1F53C", "j": ["arrow", "button", "red", "upwards button"]}, "fast-up-button": {"a": "fast up button", "b": "23EB", "j": ["arrow", "double", "fast up button"]}, "downwards-button": {"a": "downwards button", "b": "1F53D", "j": ["arrow", "button", "down", "downwards button", "red"]}, "fast-down-button": {"a": "fast down button", "b": "23EC", "j": ["arrow", "double", "down", "fast down button"]}, "pause-button": {"a": "pause button", "b": "23F8", "j": ["bar", "double", "pause", "pause button", "vertical"]}, "stop-button": {"a": "stop button", "b": "23F9", "j": ["square", "stop", "stop button"]}, "record-button": {"a": "record button", "b": "23FA", "j": ["circle", "record", "record button"]}, "eject-button": {"a": "eject button", "b": "23CF", "j": ["eject", "eject button"]}, "cinema": {"a": "cinema", "b": "1F3A6", "j": ["camera", "cinema", "film", "movie"]}, "dim-button": {"a": "dim button", "b": "1F505", "j": ["brightness", "dim", "dim button", "low"]}, "bright-button": {"a": "bright button", "b": "1F506", "j": ["bright", "bright button", "brightness"]}, "antenna-bars": {"a": "antenna bars", "b": "1F4F6", "j": ["antenna", "antenna bars", "bar", "cell", "mobile", "phone"]}, "vibration-mode": {"a": "vibration mode", "b": "1F4F3", "j": ["cell", "mobile", "mode", "phone", "telephone", "vibration"]}, "mobile-phone-off": {"a": "mobile phone off", "b": "1F4F4", "j": ["cell", "mobile", "off", "phone", "telephone"]}, "female-sign": {"a": "female sign", "b": "2640", "j": ["female sign", "woman"]}, "male-sign": {"a": "male sign", "b": "2642", "j": ["male sign", "man"]}, "transgender-symbol": {"a": "transgender symbol", "b": "26A7", "j": ["transgender", "transgender symbol"]}, "multiply": {"a": "multiply", "b": "2716", "j": ["×", "cancel", "multiplication", "multiply", "sign", "x"]}, "plus": {"a": "plus", "b": "2795", "j": ["+", "math", "plus", "sign"]}, "minus": {"a": "minus", "b": "2796", "j": ["-", "−", "math", "minus", "sign"]}, "divide": {"a": "divide", "b": "2797", "j": ["÷", "divide", "division", "math", "sign"]}, "infinity": {"a": "infinity", "b": "267E", "j": ["forever", "infinity", "unbounded", "universal"]}, "double-exclamation-mark": {"a": "double exclamation mark", "b": "203C", "j": ["!", "!!", "bangbang", "double exclamation mark", "exclamation", "mark"]}, "exclamation-question-mark": {"a": "exclamation question mark", "b": "2049", "j": ["!", "!?", "?", "exclamation", "interrobang", "mark", "punctuation", "question"]}, "red-question-mark": {"a": "red question mark", "b": "2753", "j": ["?", "mark", "punctuation", "question", "red question mark"]}, "white-question-mark": {"a": "white question mark", "b": "2754", "j": ["?", "mark", "outlined", "punctuation", "question", "white question mark"]}, "white-exclamation-mark": {"a": "white exclamation mark", "b": "2755", "j": ["!", "exclamation", "mark", "outlined", "punctuation", "white exclamation mark"]}, "red-exclamation-mark": {"a": "red exclamation mark", "b": "2757", "j": ["!", "exclamation", "mark", "punctuation", "red exclamation mark"]}, "wavy-dash": {"a": "wavy dash", "b": "3030", "j": ["dash", "punctuation", "wavy"]}, "currency-exchange": {"a": "currency exchange", "b": "1F4B1", "j": ["bank", "currency", "exchange", "money"]}, "heavy-dollar-sign": {"a": "heavy dollar sign", "b": "1F4B2", "j": ["currency", "dollar", "heavy dollar sign", "money"]}, "medical-symbol": {"a": "medical symbol", "b": "2695", "j": ["aesculapius", "medical symbol", "medicine", "staff"]}, "recycling-symbol": {"a": "recycling symbol", "b": "267B", "j": ["recycle", "recycling symbol"]}, "fleurdelis": {"a": "fleur-de-lis", "b": "269C", "j": ["fleur-de-lis"]}, "trident-emblem": {"a": "trident emblem", "b": "1F531", "j": ["anchor", "emblem", "ship", "tool", "trident"]}, "name-badge": {"a": "name badge", "b": "1F4DB", "j": ["badge", "name"]}, "japanese-symbol-for-beginner": {"a": "Japanese symbol for beginner", "b": "1F530", "j": ["beginner", "chevron", "Japanese", "Japanese symbol for beginner", "leaf"]}, "hollow-red-circle": {"a": "hollow red circle", "b": "2B55", "j": ["circle", "hollow red circle", "large", "o", "red"]}, "check-mark-button": {"a": "check mark button", "b": "2705", "j": ["✓", "button", "check", "mark"]}, "check-box-with-check": {"a": "check box with check", "b": "2611", "j": ["✓", "box", "check", "check box with check"]}, "check-mark": {"a": "check mark", "b": "2714", "j": ["✓", "check", "mark"]}, "cross-mark": {"a": "cross mark", "b": "274C", "j": ["×", "cancel", "cross", "mark", "multiplication", "multiply", "x"]}, "cross-mark-button": {"a": "cross mark button", "b": "274E", "j": ["×", "cross mark button", "mark", "square", "x"]}, "curly-loop": {"a": "curly loop", "b": "27B0", "j": ["curl", "curly loop", "loop"]}, "double-curly-loop": {"a": "double curly loop", "b": "27BF", "j": ["curl", "double", "double curly loop", "loop"]}, "part-alternation-mark": {"a": "part alternation mark", "b": "303D", "j": ["mark", "part", "part alternation mark"]}, "eightspoked-asterisk": {"a": "eight-spoked asterisk", "b": "2733", "j": ["*", "asterisk", "eight-spoked asterisk"]}, "eightpointed-star": {"a": "eight-pointed star", "b": "2734", "j": ["*", "eight-pointed star", "star"]}, "sparkle": {"a": "sparkle", "b": "2747", "j": ["*", "sparkle"]}, "copyright": {"a": "copyright", "b": "00A9", "j": ["c", "copyright"]}, "registered": {"a": "registered", "b": "00AE", "j": ["r", "registered"]}, "trade-mark": {"a": "trade mark", "b": "2122", "j": ["mark", "tm", "trade mark", "trademark"]}, "keycap": {"a": "keycap: *", "b": "002A-FE0F-20E3", "j": ["keycap"]}, "keycap-0": {"a": "keycap: 0", "b": "0030-FE0F-20E3", "j": ["keycap"]}, "keycap-1": {"a": "keycap: 1", "b": "0031-FE0F-20E3", "j": ["keycap"]}, "keycap-2": {"a": "keycap: 2", "b": "0032-FE0F-20E3", "j": ["keycap"]}, "keycap-3": {"a": "keycap: 3", "b": "0033-FE0F-20E3", "j": ["keycap"]}, "keycap-4": {"a": "keycap: 4", "b": "0034-FE0F-20E3", "j": ["keycap"]}, "keycap-5": {"a": "keycap: 5", "b": "0035-FE0F-20E3", "j": ["keycap"]}, "keycap-6": {"a": "keycap: 6", "b": "0036-FE0F-20E3", "j": ["keycap"]}, "keycap-7": {"a": "keycap: 7", "b": "0037-FE0F-20E3", "j": ["keycap"]}, "keycap-8": {"a": "keycap: 8", "b": "0038-FE0F-20E3", "j": ["keycap"]}, "keycap-9": {"a": "keycap: 9", "b": "0039-FE0F-20E3", "j": ["keycap"]}, "keycap-10": {"a": "keycap: 10", "b": "1F51F", "j": ["keycap"]}, "input-latin-uppercase": {"a": "input latin uppercase", "b": "1F520", "j": ["ABCD", "input", "latin", "letters", "uppercase"]}, "input-latin-lowercase": {"a": "input latin lowercase", "b": "1F521", "j": ["abcd", "input", "latin", "letters", "lowercase"]}, "input-numbers": {"a": "input numbers", "b": "1F522", "j": ["1234", "input", "numbers"]}, "input-symbols": {"a": "input symbols", "b": "1F523", "j": ["〒♪&%", "input", "input symbols"]}, "input-latin-letters": {"a": "input latin letters", "b": "1F524", "j": ["abc", "alphabet", "input", "latin", "letters"]}, "a-button-blood-type": {"a": "A button (blood type)", "b": "1F170", "j": ["a", "A button (blood type)", "blood type"]}, "ab-button-blood-type": {"a": "AB button (blood type)", "b": "1F18E", "j": ["ab", "AB button (blood type)", "blood type"]}, "b-button-blood-type": {"a": "B button (blood type)", "b": "1F171", "j": ["b", "B button (blood type)", "blood type"]}, "cl-button": {"a": "CL button", "b": "1F191", "j": ["cl", "CL button"]}, "cool-button": {"a": "COOL button", "b": "1F192", "j": ["cool", "COOL button"]}, "free-button": {"a": "FREE button", "b": "1F193", "j": ["free", "FREE button"]}, "information": {"a": "information", "b": "2139", "j": ["i", "information"]}, "id-button": {"a": "ID button", "b": "1F194", "j": ["id", "ID button", "identity"]}, "circled-m": {"a": "circled M", "b": "24C2", "j": ["circle", "circled M", "m"]}, "new-button": {"a": "NEW button", "b": "1F195", "j": ["new", "NEW button"]}, "ng-button": {"a": "NG button", "b": "1F196", "j": ["ng", "NG button"]}, "o-button-blood-type": {"a": "O button (blood type)", "b": "1F17E", "j": ["blood type", "o", "O button (blood type)"]}, "ok-button": {"a": "OK button", "b": "1F197", "j": ["OK", "OK button"]}, "p-button": {"a": "P button", "b": "1F17F", "j": ["P button", "parking"]}, "sos-button": {"a": "SOS button", "b": "1F198", "j": ["help", "sos", "SOS button"]}, "up-button": {"a": "UP! button", "b": "1F199", "j": ["mark", "up", "UP! button"]}, "vs-button": {"a": "VS button", "b": "1F19A", "j": ["versus", "vs", "VS button"]}, "japanese-here-button": {"a": "Japanese “here” button", "b": "1F201", "j": ["“here”", "Japanese", "Japanese “here” button", "katakana", "ココ"]}, "japanese-service-charge-button": {"a": "Japanese “service charge” button", "b": "1F202", "j": ["“service charge”", "Japanese", "Japanese “service charge” button", "katakana", "サ"]}, "japanese-monthly-amount-button": {"a": "Japanese “monthly amount” button", "b": "1F237", "j": ["“monthly amount”", "ideograph", "Japanese", "Japanese “monthly amount” button", "月"]}, "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", "有"]}, "japanese-reserved-button": {"a": "Japanese “reserved” button", "b": "1F22F", "j": ["“reserved”", "ideograph", "Japanese", "Japanese “reserved” button", "指"]}, "japanese-bargain-button": {"a": "Japanese “bargain” button", "b": "1F250", "j": ["“bargain”", "ideograph", "Japanese", "Japanese “bargain” button", "得"]}, "japanese-discount-button": {"a": "Japanese “discount” button", "b": "1F239", "j": ["“discount”", "ideograph", "Japanese", "Japanese “discount” button", "割"]}, "japanese-free-of-charge-button": {"a": "Japanese “free of charge” button", "b": "1F21A", "j": ["“free of charge”", "ideograph", "Japanese", "Japanese “free of charge” button", "無"]}, "japanese-prohibited-button": {"a": "Japanese “prohibited” button", "b": "1F232", "j": ["“prohibited”", "ideograph", "Japanese", "Japanese “prohibited” button", "禁"]}, "japanese-acceptable-button": {"a": "Japanese “acceptable” button", "b": "1F251", "j": ["“acceptable”", "ideograph", "Japanese", "Japanese “acceptable” button", "可"]}, "japanese-application-button": {"a": "Japanese “application” button", "b": "1F238", "j": ["“application”", "ideograph", "Japanese", "Japanese “application” button", "申"]}, "japanese-passing-grade-button": {"a": "Japanese “passing grade” button", "b": "1F234", "j": ["“passing grade”", "ideograph", "Japanese", "Japanese “passing grade” button", "合"]}, "japanese-vacancy-button": {"a": "Japanese “vacancy” button", "b": "1F233", "j": ["“vacancy”", "ideograph", "Japanese", "Japanese “vacancy” button", "空"]}, "japanese-congratulations-button": {"a": "Japanese “congratulations” button", "b": "3297", "j": ["“congratulations”", "ideograph", "Japanese", "Japanese “congratulations” button", "祝"]}, "japanese-secret-button": {"a": "Japanese “secret” button", "b": "3299", "j": ["“secret”", "ideograph", "Japanese", "Japanese “secret” button", "秘"]}, "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-no-vacancy-button": {"a": "Japanese “no vacancy” button", "b": "1F235", "j": ["“no vacancy”", "ideograph", "Japanese", "Japanese “no vacancy” button", "満"]}, "red-circle": {"a": "red circle", "b": "1F534", "j": ["circle", "geometric", "red"]}, "orange-circle": {"a": "orange circle", "b": "1F7E0", "j": ["circle", "orange"]}, "yellow-circle": {"a": "yellow circle", "b": "1F7E1", "j": ["circle", "yellow"]}, "green-circle": {"a": "green circle", "b": "1F7E2", "j": ["circle", "green"]}, "blue-circle": {"a": "blue circle", "b": "1F535", "j": ["blue", "circle", "geometric"]}, "purple-circle": {"a": "purple circle", "b": "1F7E3", "j": ["circle", "purple"]}, "brown-circle": {"a": "brown circle", "b": "1F7E4", "j": ["brown", "circle"]}, "black-circle": {"a": "black circle", "b": "26AB", "j": ["black circle", "circle", "geometric"]}, "white-circle": {"a": "white circle", "b": "26AA", "j": ["circle", "geometric", "white circle"]}, "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": ["black large square", "geometric", "square"]}, "white-large-square": {"a": "white large square", "b": "2B1C", "j": ["geometric", "square", "white large square"]}, "black-medium-square": {"a": "black medium square", "b": "25FC", "j": ["black medium square", "geometric", "square"]}, "white-medium-square": {"a": "white medium square", "b": "25FB", "j": ["geometric", "square", "white medium square"]}, "black-mediumsmall-square": {"a": "black medium-small square", "b": "25FE", "j": ["black medium-small square", "geometric", "square"]}, "white-mediumsmall-square": {"a": "white medium-small square", "b": "25FD", "j": ["geometric", "square", "white medium-small square"]}, "black-small-square": {"a": "black small square", "b": "25AA", "j": ["black small square", "geometric", "square"]}, "white-small-square": {"a": "white small square", "b": "25AB", "j": ["geometric", "square", "white small square"]}, "large-orange-diamond": {"a": "large orange diamond", "b": "1F536", "j": ["diamond", "geometric", "large orange diamond", "orange"]}, "large-blue-diamond": {"a": "large blue diamond", "b": "1F537", "j": ["blue", "diamond", "geometric", "large blue diamond"]}, "small-orange-diamond": {"a": "small orange diamond", "b": "1F538", "j": ["diamond", "geometric", "orange", "small orange diamond"]}, "small-blue-diamond": {"a": "small blue diamond", "b": "1F539", "j": ["blue", "diamond", "geometric", "small blue diamond"]}, "red-triangle-pointed-up": {"a": "red triangle pointed up", "b": "1F53A", "j": ["geometric", "red", "red triangle pointed up"]}, "red-triangle-pointed-down": {"a": "red triangle pointed down", "b": "1F53B", "j": ["down", "geometric", "red", "red triangle pointed down"]}, "diamond-with-a-dot": {"a": "diamond with a dot", "b": "1F4A0", "j": ["comic", "diamond", "diamond with a dot", "geometric", "inside"]}, "radio-button": {"a": "radio button", "b": "1F518", "j": ["button", "geometric", "radio"]}, "white-square-button": {"a": "white square button", "b": "1F533", "j": ["button", "geometric", "outlined", "square", "white square button"]}, "black-square-button": {"a": "black square button", "b": "1F532", "j": ["black square button", "button", "geometric", "square"]}, "chequered-flag": {"a": "chequered flag", "b": "1F3C1", "j": ["checkered", "chequered", "chequered flag", "racing"]}, "triangular-flag": {"a": "triangular flag", "b": "1F6A9", "j": ["post", "triangular flag"]}, "crossed-flags": {"a": "crossed flags", "b": "1F38C", "j": ["celebration", "cross", "crossed", "crossed flags", "Japanese"]}, "black-flag": {"a": "black flag", "b": "1F3F4", "j": ["black flag", "waving"]}, "white-flag": {"a": "white flag", "b": "1F3F3", "j": ["waving", "white flag"]}, "rainbow-flag": {"a": "rainbow flag", "b": "1F3F3-FE0F-200D-1F308", "j": ["pride", "rainbow", "rainbow flag"]}, "transgender-flag": {"a": "transgender flag", "b": "1F3F3-FE0F-200D-26A7-FE0F", "j": ["flag", "light blue", "pink", "transgender", "white"]}, "pirate-flag": {"a": "pirate flag", "b": "1F3F4-200D-2620-FE0F", "j": ["Jolly Roger", "pirate", "pirate flag", "plunder", "treasure"]}, "flag-ascension-island": {"a": "flag: Ascension Island", "b": "1F1E6-1F1E8", "j": ["flag"]}, "flag-andorra": {"a": "flag: Andorra", "b": "1F1E6-1F1E9", "j": ["flag"]}, "flag-united-arab-emirates": {"a": "flag: United Arab Emirates", "b": "1F1E6-1F1EA", "j": ["flag"]}, "flag-afghanistan": {"a": "flag: Afghanistan", "b": "1F1E6-1F1EB", "j": ["flag"]}, "flag-antigua--barbuda": {"a": "flag: Antigua & Barbuda", "b": "1F1E6-1F1EC", "j": ["flag"]}, "flag-anguilla": {"a": "flag: Anguilla", "b": "1F1E6-1F1EE", "j": ["flag"]}, "flag-albania": {"a": "flag: Albania", "b": "1F1E6-1F1F1", "j": ["flag"]}, "flag-armenia": {"a": "flag: Armenia", "b": "1F1E6-1F1F2", "j": ["flag"]}, "flag-angola": {"a": "flag: Angola", "b": "1F1E6-1F1F4", "j": ["flag"]}, "flag-antarctica": {"a": "flag: Antarctica", "b": "1F1E6-1F1F6", "j": ["flag"]}, "flag-argentina": {"a": "flag: Argentina", "b": "1F1E6-1F1F7", "j": ["flag"]}, "flag-american-samoa": {"a": "flag: American Samoa", "b": "1F1E6-1F1F8", "j": ["flag"]}, "flag-austria": {"a": "flag: Austria", "b": "1F1E6-1F1F9", "j": ["flag"]}, "flag-australia": {"a": "flag: Australia", "b": "1F1E6-1F1FA", "j": ["flag"]}, "flag-aruba": {"a": "flag: Aruba", "b": "1F1E6-1F1FC", "j": ["flag"]}, "flag-land-islands": {"a": "flag: Åland Islands", "b": "1F1E6-1F1FD", "j": ["flag"]}, "flag-azerbaijan": {"a": "flag: Azerbaijan", "b": "1F1E6-1F1FF", "j": ["flag"]}, "flag-bosnia--herzegovina": {"a": "flag: Bosnia & Herzegovina", "b": "1F1E7-1F1E6", "j": ["flag"]}, "flag-barbados": {"a": "flag: Barbados", "b": "1F1E7-1F1E7", "j": ["flag"]}, "flag-bangladesh": {"a": "flag: Bangladesh", "b": "1F1E7-1F1E9", "j": ["flag"]}, "flag-belgium": {"a": "flag: Belgium", "b": "1F1E7-1F1EA", "j": ["flag"]}, "flag-burkina-faso": {"a": "flag: Burkina Faso", "b": "1F1E7-1F1EB", "j": ["flag"]}, "flag-bulgaria": {"a": "flag: Bulgaria", "b": "1F1E7-1F1EC", "j": ["flag"]}, "flag-bahrain": {"a": "flag: Bahrain", "b": "1F1E7-1F1ED", "j": ["flag"]}, "flag-burundi": {"a": "flag: Burundi", "b": "1F1E7-1F1EE", "j": ["flag"]}, "flag-benin": {"a": "flag: Benin", "b": "1F1E7-1F1EF", "j": ["flag"]}, "flag-st-barthlemy": {"a": "flag: St. Barthélemy", "b": "1F1E7-1F1F1", "j": ["flag"]}, "flag-bermuda": {"a": "flag: Bermuda", "b": "1F1E7-1F1F2", "j": ["flag"]}, "flag-brunei": {"a": "flag: Brunei", "b": "1F1E7-1F1F3", "j": ["flag"]}, "flag-bolivia": {"a": "flag: Bolivia", "b": "1F1E7-1F1F4", "j": ["flag"]}, "flag-caribbean-netherlands": {"a": "flag: Caribbean Netherlands", "b": "1F1E7-1F1F6", "j": ["flag"]}, "flag-brazil": {"a": "flag: Brazil", "b": "1F1E7-1F1F7", "j": ["flag"]}, "flag-bahamas": {"a": "flag: Bahamas", "b": "1F1E7-1F1F8", "j": ["flag"]}, "flag-bhutan": {"a": "flag: Bhutan", "b": "1F1E7-1F1F9", "j": ["flag"]}, "flag-bouvet-island": {"a": "flag: Bouvet Island", "b": "1F1E7-1F1FB", "j": ["flag"]}, "flag-botswana": {"a": "flag: Botswana", "b": "1F1E7-1F1FC", "j": ["flag"]}, "flag-belarus": {"a": "flag: Belarus", "b": "1F1E7-1F1FE", "j": ["flag"]}, "flag-belize": {"a": "flag: Belize", "b": "1F1E7-1F1FF", "j": ["flag"]}, "flag-canada": {"a": "flag: Canada", "b": "1F1E8-1F1E6", "j": ["flag"]}, "flag-cocos-keeling-islands": {"a": "flag: Cocos (Keeling) Islands", "b": "1F1E8-1F1E8", "j": ["flag"]}, "flag-congo--kinshasa": {"a": "flag: Congo - Kinshasa", "b": "1F1E8-1F1E9", "j": ["flag"]}, "flag-central-african-republic": {"a": "flag: Central African Republic", "b": "1F1E8-1F1EB", "j": ["flag"]}, "flag-congo--brazzaville": {"a": "flag: Congo - Brazzaville", "b": "1F1E8-1F1EC", "j": ["flag"]}, "flag-switzerland": {"a": "flag: Switzerland", "b": "1F1E8-1F1ED", "j": ["flag"]}, "flag-cte-divoire": {"a": "flag: Côte d’Ivoire", "b": "1F1E8-1F1EE", "j": ["flag"]}, "flag-cook-islands": {"a": "flag: Cook Islands", "b": "1F1E8-1F1F0", "j": ["flag"]}, "flag-chile": {"a": "flag: Chile", "b": "1F1E8-1F1F1", "j": ["flag"]}, "flag-cameroon": {"a": "flag: Cameroon", "b": "1F1E8-1F1F2", "j": ["flag"]}, "flag-china": {"a": "flag: China", "b": "1F1E8-1F1F3", "j": ["flag"]}, "flag-colombia": {"a": "flag: Colombia", "b": "1F1E8-1F1F4", "j": ["flag"]}, "flag-clipperton-island": {"a": "flag: Clipperton Island", "b": "1F1E8-1F1F5", "j": ["flag"]}, "flag-costa-rica": {"a": "flag: Costa Rica", "b": "1F1E8-1F1F7", "j": ["flag"]}, "flag-cuba": {"a": "flag: Cuba", "b": "1F1E8-1F1FA", "j": ["flag"]}, "flag-cape-verde": {"a": "flag: Cape Verde", "b": "1F1E8-1F1FB", "j": ["flag"]}, "flag-curaao": {"a": "flag: Curaçao", "b": "1F1E8-1F1FC", "j": ["flag"]}, "flag-christmas-island": {"a": "flag: Christmas Island", "b": "1F1E8-1F1FD", "j": ["flag"]}, "flag-cyprus": {"a": "flag: Cyprus", "b": "1F1E8-1F1FE", "j": ["flag"]}, "flag-czechia": {"a": "flag: Czechia", "b": "1F1E8-1F1FF", "j": ["flag"]}, "flag-germany": {"a": "flag: Germany", "b": "1F1E9-1F1EA", "j": ["flag"]}, "flag-diego-garcia": {"a": "flag: Diego Garcia", "b": "1F1E9-1F1EC", "j": ["flag"]}, "flag-djibouti": {"a": "flag: Djibouti", "b": "1F1E9-1F1EF", "j": ["flag"]}, "flag-denmark": {"a": "flag: Denmark", "b": "1F1E9-1F1F0", "j": ["flag"]}, "flag-dominica": {"a": "flag: Dominica", "b": "1F1E9-1F1F2", "j": ["flag"]}, "flag-dominican-republic": {"a": "flag: Dominican Republic", "b": "1F1E9-1F1F4", "j": ["flag"]}, "flag-algeria": {"a": "flag: Algeria", "b": "1F1E9-1F1FF", "j": ["flag"]}, "flag-ceuta--melilla": {"a": "flag: Ceuta & Melilla", "b": "1F1EA-1F1E6", "j": ["flag"]}, "flag-ecuador": {"a": "flag: Ecuador", "b": "1F1EA-1F1E8", "j": ["flag"]}, "flag-estonia": {"a": "flag: Estonia", "b": "1F1EA-1F1EA", "j": ["flag"]}, "flag-egypt": {"a": "flag: Egypt", "b": "1F1EA-1F1EC", "j": ["flag"]}, "flag-western-sahara": {"a": "flag: Western Sahara", "b": "1F1EA-1F1ED", "j": ["flag"]}, "flag-eritrea": {"a": "flag: Eritrea", "b": "1F1EA-1F1F7", "j": ["flag"]}, "flag-spain": {"a": "flag: Spain", "b": "1F1EA-1F1F8", "j": ["flag"]}, "flag-ethiopia": {"a": "flag: Ethiopia", "b": "1F1EA-1F1F9", "j": ["flag"]}, "flag-european-union": {"a": "flag: European Union", "b": "1F1EA-1F1FA", "j": ["flag"]}, "flag-finland": {"a": "flag: Finland", "b": "1F1EB-1F1EE", "j": ["flag"]}, "flag-fiji": {"a": "flag: Fiji", "b": "1F1EB-1F1EF", "j": ["flag"]}, "flag-falkland-islands": {"a": "flag: Falkland Islands", "b": "1F1EB-1F1F0", "j": ["flag"]}, "flag-micronesia": {"a": "flag: Micronesia", "b": "1F1EB-1F1F2", "j": ["flag"]}, "flag-faroe-islands": {"a": "flag: Faroe Islands", "b": "1F1EB-1F1F4", "j": ["flag"]}, "flag-france": {"a": "flag: France", "b": "1F1EB-1F1F7", "j": ["flag"]}, "flag-gabon": {"a": "flag: Gabon", "b": "1F1EC-1F1E6", "j": ["flag"]}, "flag-united-kingdom": {"a": "flag: United Kingdom", "b": "1F1EC-1F1E7", "j": ["flag"]}, "flag-grenada": {"a": "flag: Grenada", "b": "1F1EC-1F1E9", "j": ["flag"]}, "flag-georgia": {"a": "flag: Georgia", "b": "1F1EC-1F1EA", "j": ["flag"]}, "flag-french-guiana": {"a": "flag: French Guiana", "b": "1F1EC-1F1EB", "j": ["flag"]}, "flag-guernsey": {"a": "flag: Guernsey", "b": "1F1EC-1F1EC", "j": ["flag"]}, "flag-ghana": {"a": "flag: Ghana", "b": "1F1EC-1F1ED", "j": ["flag"]}, "flag-gibraltar": {"a": "flag: Gibraltar", "b": "1F1EC-1F1EE", "j": ["flag"]}, "flag-greenland": {"a": "flag: Greenland", "b": "1F1EC-1F1F1", "j": ["flag"]}, "flag-gambia": {"a": "flag: Gambia", "b": "1F1EC-1F1F2", "j": ["flag"]}, "flag-guinea": {"a": "flag: Guinea", "b": "1F1EC-1F1F3", "j": ["flag"]}, "flag-guadeloupe": {"a": "flag: Guadeloupe", "b": "1F1EC-1F1F5", "j": ["flag"]}, "flag-equatorial-guinea": {"a": "flag: Equatorial Guinea", "b": "1F1EC-1F1F6", "j": ["flag"]}, "flag-greece": {"a": "flag: Greece", "b": "1F1EC-1F1F7", "j": ["flag"]}, "flag-south-georgia--south-sandwich-islands": {"a": "flag: South Georgia & South Sandwich Islands", "b": "1F1EC-1F1F8", "j": ["flag"]}, "flag-guatemala": {"a": "flag: Guatemala", "b": "1F1EC-1F1F9", "j": ["flag"]}, "flag-guam": {"a": "flag: Guam", "b": "1F1EC-1F1FA", "j": ["flag"]}, "flag-guineabissau": {"a": "flag: Guinea-Bissau", "b": "1F1EC-1F1FC", "j": ["flag"]}, "flag-guyana": {"a": "flag: Guyana", "b": "1F1EC-1F1FE", "j": ["flag"]}, "flag-hong-kong-sar-china": {"a": "flag: Hong Kong SAR China", "b": "1F1ED-1F1F0", "j": ["flag"]}, "flag-heard--mcdonald-islands": {"a": "flag: Heard & McDonald Islands", "b": "1F1ED-1F1F2", "j": ["flag"]}, "flag-honduras": {"a": "flag: Honduras", "b": "1F1ED-1F1F3", "j": ["flag"]}, "flag-croatia": {"a": "flag: Croatia", "b": "1F1ED-1F1F7", "j": ["flag"]}, "flag-haiti": {"a": "flag: Haiti", "b": "1F1ED-1F1F9", "j": ["flag"]}, "flag-hungary": {"a": "flag: Hungary", "b": "1F1ED-1F1FA", "j": ["flag"]}, "flag-canary-islands": {"a": "flag: Canary Islands", "b": "1F1EE-1F1E8", "j": ["flag"]}, "flag-indonesia": {"a": "flag: Indonesia", "b": "1F1EE-1F1E9", "j": ["flag"]}, "flag-ireland": {"a": "flag: Ireland", "b": "1F1EE-1F1EA", "j": ["flag"]}, "flag-israel": {"a": "flag: Israel", "b": "1F1EE-1F1F1", "j": ["flag"]}, "flag-isle-of-man": {"a": "flag: Isle of Man", "b": "1F1EE-1F1F2", "j": ["flag"]}, "flag-india": {"a": "flag: India", "b": "1F1EE-1F1F3", "j": ["flag"]}, "flag-british-indian-ocean-territory": {"a": "flag: British Indian Ocean Territory", "b": "1F1EE-1F1F4", "j": ["flag"]}, "flag-iraq": {"a": "flag: Iraq", "b": "1F1EE-1F1F6", "j": ["flag"]}, "flag-iran": {"a": "flag: Iran", "b": "1F1EE-1F1F7", "j": ["flag"]}, "flag-iceland": {"a": "flag: Iceland", "b": "1F1EE-1F1F8", "j": ["flag"]}, "flag-italy": {"a": "flag: Italy", "b": "1F1EE-1F1F9", "j": ["flag"]}, "flag-jersey": {"a": "flag: Jersey", "b": "1F1EF-1F1EA", "j": ["flag"]}, "flag-jamaica": {"a": "flag: Jamaica", "b": "1F1EF-1F1F2", "j": ["flag"]}, "flag-jordan": {"a": "flag: Jordan", "b": "1F1EF-1F1F4", "j": ["flag"]}, "flag-japan": {"a": "flag: Japan", "b": "1F1EF-1F1F5", "j": ["flag"]}, "flag-kenya": {"a": "flag: Kenya", "b": "1F1F0-1F1EA", "j": ["flag"]}, "flag-kyrgyzstan": {"a": "flag: Kyrgyzstan", "b": "1F1F0-1F1EC", "j": ["flag"]}, "flag-cambodia": {"a": "flag: Cambodia", "b": "1F1F0-1F1ED", "j": ["flag"]}, "flag-kiribati": {"a": "flag: Kiribati", "b": "1F1F0-1F1EE", "j": ["flag"]}, "flag-comoros": {"a": "flag: Comoros", "b": "1F1F0-1F1F2", "j": ["flag"]}, "flag-st-kitts--nevis": {"a": "flag: St. Kitts & Nevis", "b": "1F1F0-1F1F3", "j": ["flag"]}, "flag-north-korea": {"a": "flag: North Korea", "b": "1F1F0-1F1F5", "j": ["flag"]}, "flag-south-korea": {"a": "flag: South Korea", "b": "1F1F0-1F1F7", "j": ["flag"]}, "flag-kuwait": {"a": "flag: Kuwait", "b": "1F1F0-1F1FC", "j": ["flag"]}, "flag-cayman-islands": {"a": "flag: Cayman Islands", "b": "1F1F0-1F1FE", "j": ["flag"]}, "flag-kazakhstan": {"a": "flag: Kazakhstan", "b": "1F1F0-1F1FF", "j": ["flag"]}, "flag-laos": {"a": "flag: Laos", "b": "1F1F1-1F1E6", "j": ["flag"]}, "flag-lebanon": {"a": "flag: Lebanon", "b": "1F1F1-1F1E7", "j": ["flag"]}, "flag-st-lucia": {"a": "flag: St. Lucia", "b": "1F1F1-1F1E8", "j": ["flag"]}, "flag-liechtenstein": {"a": "flag: Liechtenstein", "b": "1F1F1-1F1EE", "j": ["flag"]}, "flag-sri-lanka": {"a": "flag: Sri Lanka", "b": "1F1F1-1F1F0", "j": ["flag"]}, "flag-liberia": {"a": "flag: Liberia", "b": "1F1F1-1F1F7", "j": ["flag"]}, "flag-lesotho": {"a": "flag: Lesotho", "b": "1F1F1-1F1F8", "j": ["flag"]}, "flag-lithuania": {"a": "flag: Lithuania", "b": "1F1F1-1F1F9", "j": ["flag"]}, "flag-luxembourg": {"a": "flag: Luxembourg", "b": "1F1F1-1F1FA", "j": ["flag"]}, "flag-latvia": {"a": "flag: Latvia", "b": "1F1F1-1F1FB", "j": ["flag"]}, "flag-libya": {"a": "flag: Libya", "b": "1F1F1-1F1FE", "j": ["flag"]}, "flag-morocco": {"a": "flag: Morocco", "b": "1F1F2-1F1E6", "j": ["flag"]}, "flag-monaco": {"a": "flag: Monaco", "b": "1F1F2-1F1E8", "j": ["flag"]}, "flag-moldova": {"a": "flag: Moldova", "b": "1F1F2-1F1E9", "j": ["flag"]}, "flag-montenegro": {"a": "flag: Montenegro", "b": "1F1F2-1F1EA", "j": ["flag"]}, "flag-st-martin": {"a": "flag: St. Martin", "b": "1F1F2-1F1EB", "j": ["flag"]}, "flag-madagascar": {"a": "flag: Madagascar", "b": "1F1F2-1F1EC", "j": ["flag"]}, "flag-marshall-islands": {"a": "flag: Marshall Islands", "b": "1F1F2-1F1ED", "j": ["flag"]}, "flag-north-macedonia": {"a": "flag: North Macedonia", "b": "1F1F2-1F1F0", "j": ["flag"]}, "flag-mali": {"a": "flag: Mali", "b": "1F1F2-1F1F1", "j": ["flag"]}, "flag-myanmar-burma": {"a": "flag: Myanmar (Burma)", "b": "1F1F2-1F1F2", "j": ["flag"]}, "flag-mongolia": {"a": "flag: Mongolia", "b": "1F1F2-1F1F3", "j": ["flag"]}, "flag-macao-sar-china": {"a": "flag: Macao SAR China", "b": "1F1F2-1F1F4", "j": ["flag"]}, "flag-northern-mariana-islands": {"a": "flag: Northern Mariana Islands", "b": "1F1F2-1F1F5", "j": ["flag"]}, "flag-martinique": {"a": "flag: Martinique", "b": "1F1F2-1F1F6", "j": ["flag"]}, "flag-mauritania": {"a": "flag: Mauritania", "b": "1F1F2-1F1F7", "j": ["flag"]}, "flag-montserrat": {"a": "flag: Montserrat", "b": "1F1F2-1F1F8", "j": ["flag"]}, "flag-malta": {"a": "flag: Malta", "b": "1F1F2-1F1F9", "j": ["flag"]}, "flag-mauritius": {"a": "flag: Mauritius", "b": "1F1F2-1F1FA", "j": ["flag"]}, "flag-maldives": {"a": "flag: Maldives", "b": "1F1F2-1F1FB", "j": ["flag"]}, "flag-malawi": {"a": "flag: Malawi", "b": "1F1F2-1F1FC", "j": ["flag"]}, "flag-mexico": {"a": "flag: Mexico", "b": "1F1F2-1F1FD", "j": ["flag"]}, "flag-malaysia": {"a": "flag: Malaysia", "b": "1F1F2-1F1FE", "j": ["flag"]}, "flag-mozambique": {"a": "flag: Mozambique", "b": "1F1F2-1F1FF", "j": ["flag"]}, "flag-namibia": {"a": "flag: Namibia", "b": "1F1F3-1F1E6", "j": ["flag"]}, "flag-new-caledonia": {"a": "flag: New Caledonia", "b": "1F1F3-1F1E8", "j": ["flag"]}, "flag-niger": {"a": "flag: Niger", "b": "1F1F3-1F1EA", "j": ["flag"]}, "flag-norfolk-island": {"a": "flag: Norfolk Island", "b": "1F1F3-1F1EB", "j": ["flag"]}, "flag-nigeria": {"a": "flag: Nigeria", "b": "1F1F3-1F1EC", "j": ["flag"]}, "flag-nicaragua": {"a": "flag: Nicaragua", "b": "1F1F3-1F1EE", "j": ["flag"]}, "flag-netherlands": {"a": "flag: Netherlands", "b": "1F1F3-1F1F1", "j": ["flag"]}, "flag-norway": {"a": "flag: Norway", "b": "1F1F3-1F1F4", "j": ["flag"]}, "flag-nepal": {"a": "flag: Nepal", "b": "1F1F3-1F1F5", "j": ["flag"]}, "flag-nauru": {"a": "flag: Nauru", "b": "1F1F3-1F1F7", "j": ["flag"]}, "flag-niue": {"a": "flag: Niue", "b": "1F1F3-1F1FA", "j": ["flag"]}, "flag-new-zealand": {"a": "flag: New Zealand", "b": "1F1F3-1F1FF", "j": ["flag"]}, "flag-oman": {"a": "flag: Oman", "b": "1F1F4-1F1F2", "j": ["flag"]}, "flag-panama": {"a": "flag: Panama", "b": "1F1F5-1F1E6", "j": ["flag"]}, "flag-peru": {"a": "flag: Peru", "b": "1F1F5-1F1EA", "j": ["flag"]}, "flag-french-polynesia": {"a": "flag: French Polynesia", "b": "1F1F5-1F1EB", "j": ["flag"]}, "flag-papua-new-guinea": {"a": "flag: Papua New Guinea", "b": "1F1F5-1F1EC", "j": ["flag"]}, "flag-philippines": {"a": "flag: Philippines", "b": "1F1F5-1F1ED", "j": ["flag"]}, "flag-pakistan": {"a": "flag: Pakistan", "b": "1F1F5-1F1F0", "j": ["flag"]}, "flag-poland": {"a": "flag: Poland", "b": "1F1F5-1F1F1", "j": ["flag"]}, "flag-st-pierre--miquelon": {"a": "flag: St. Pierre & Miquelon", "b": "1F1F5-1F1F2", "j": ["flag"]}, "flag-pitcairn-islands": {"a": "flag: Pitcairn Islands", "b": "1F1F5-1F1F3", "j": ["flag"]}, "flag-puerto-rico": {"a": "flag: Puerto Rico", "b": "1F1F5-1F1F7", "j": ["flag"]}, "flag-palestinian-territories": {"a": "flag: Palestinian Territories", "b": "1F1F5-1F1F8", "j": ["flag"]}, "flag-portugal": {"a": "flag: Portugal", "b": "1F1F5-1F1F9", "j": ["flag"]}, "flag-palau": {"a": "flag: Palau", "b": "1F1F5-1F1FC", "j": ["flag"]}, "flag-paraguay": {"a": "flag: Paraguay", "b": "1F1F5-1F1FE", "j": ["flag"]}, "flag-qatar": {"a": "flag: Qatar", "b": "1F1F6-1F1E6", "j": ["flag"]}, "flag-runion": {"a": "flag: Réunion", "b": "1F1F7-1F1EA", "j": ["flag"]}, "flag-romania": {"a": "flag: Romania", "b": "1F1F7-1F1F4", "j": ["flag"]}, "flag-serbia": {"a": "flag: Serbia", "b": "1F1F7-1F1F8", "j": ["flag"]}, "flag-russia": {"a": "flag: Russia", "b": "1F1F7-1F1FA", "j": ["flag"]}, "flag-rwanda": {"a": "flag: Rwanda", "b": "1F1F7-1F1FC", "j": ["flag"]}, "flag-saudi-arabia": {"a": "flag: Saudi Arabia", "b": "1F1F8-1F1E6", "j": ["flag"]}, "flag-solomon-islands": {"a": "flag: Solomon Islands", "b": "1F1F8-1F1E7", "j": ["flag"]}, "flag-seychelles": {"a": "flag: Seychelles", "b": "1F1F8-1F1E8", "j": ["flag"]}, "flag-sudan": {"a": "flag: Sudan", "b": "1F1F8-1F1E9", "j": ["flag"]}, "flag-sweden": {"a": "flag: Sweden", "b": "1F1F8-1F1EA", "j": ["flag"]}, "flag-singapore": {"a": "flag: Singapore", "b": "1F1F8-1F1EC", "j": ["flag"]}, "flag-st-helena": {"a": "flag: St. Helena", "b": "1F1F8-1F1ED", "j": ["flag"]}, "flag-slovenia": {"a": "flag: Slovenia", "b": "1F1F8-1F1EE", "j": ["flag"]}, "flag-svalbard--jan-mayen": {"a": "flag: Svalbard & Jan Mayen", "b": "1F1F8-1F1EF", "j": ["flag"]}, "flag-slovakia": {"a": "flag: Slovakia", "b": "1F1F8-1F1F0", "j": ["flag"]}, "flag-sierra-leone": {"a": "flag: Sierra Leone", "b": "1F1F8-1F1F1", "j": ["flag"]}, "flag-san-marino": {"a": "flag: San Marino", "b": "1F1F8-1F1F2", "j": ["flag"]}, "flag-senegal": {"a": "flag: Senegal", "b": "1F1F8-1F1F3", "j": ["flag"]}, "flag-somalia": {"a": "flag: Somalia", "b": "1F1F8-1F1F4", "j": ["flag"]}, "flag-suriname": {"a": "flag: Suriname", "b": "1F1F8-1F1F7", "j": ["flag"]}, "flag-south-sudan": {"a": "flag: South Sudan", "b": "1F1F8-1F1F8", "j": ["flag"]}, "flag-so-tom--prncipe": {"a": "flag: São Tomé & Príncipe", "b": "1F1F8-1F1F9", "j": ["flag"]}, "flag-el-salvador": {"a": "flag: El Salvador", "b": "1F1F8-1F1FB", "j": ["flag"]}, "flag-sint-maarten": {"a": "flag: Sint Maarten", "b": "1F1F8-1F1FD", "j": ["flag"]}, "flag-syria": {"a": "flag: Syria", "b": "1F1F8-1F1FE", "j": ["flag"]}, "flag-eswatini": {"a": "flag: Eswatini", "b": "1F1F8-1F1FF", "j": ["flag"]}, "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-chad": {"a": "flag: Chad", "b": "1F1F9-1F1E9", "j": ["flag"]}, "flag-french-southern-territories": {"a": "flag: French Southern Territories", "b": "1F1F9-1F1EB", "j": ["flag"]}, "flag-togo": {"a": "flag: Togo", "b": "1F1F9-1F1EC", "j": ["flag"]}, "flag-thailand": {"a": "flag: Thailand", "b": "1F1F9-1F1ED", "j": ["flag"]}, "flag-tajikistan": {"a": "flag: Tajikistan", "b": "1F1F9-1F1EF", "j": ["flag"]}, "flag-tokelau": {"a": "flag: Tokelau", "b": "1F1F9-1F1F0", "j": ["flag"]}, "flag-timorleste": {"a": "flag: Timor-Leste", "b": "1F1F9-1F1F1", "j": ["flag"]}, "flag-turkmenistan": {"a": "flag: Turkmenistan", "b": "1F1F9-1F1F2", "j": ["flag"]}, "flag-tunisia": {"a": "flag: Tunisia", "b": "1F1F9-1F1F3", "j": ["flag"]}, "flag-tonga": {"a": "flag: Tonga", "b": "1F1F9-1F1F4", "j": ["flag"]}, "flag-turkey": {"a": "flag: Turkey", "b": "1F1F9-1F1F7", "j": ["flag"]}, "flag-trinidad--tobago": {"a": "flag: Trinidad & Tobago", "b": "1F1F9-1F1F9", "j": ["flag"]}, "flag-tuvalu": {"a": "flag: Tuvalu", "b": "1F1F9-1F1FB", "j": ["flag"]}, "flag-taiwan": {"a": "flag: Taiwan", "b": "1F1F9-1F1FC", "j": ["flag"]}, "flag-tanzania": {"a": "flag: Tanzania", "b": "1F1F9-1F1FF", "j": ["flag"]}, "flag-ukraine": {"a": "flag: Ukraine", "b": "1F1FA-1F1E6", "j": ["flag"]}, "flag-uganda": {"a": "flag: Uganda", "b": "1F1FA-1F1EC", "j": ["flag"]}, "flag-us-outlying-islands": {"a": "flag: U.S. Outlying Islands", "b": "1F1FA-1F1F2", "j": ["flag"]}, "flag-united-nations": {"a": "flag: United Nations", "b": "1F1FA-1F1F3", "j": ["flag"]}, "flag-united-states": {"a": "flag: United States", "b": "1F1FA-1F1F8", "j": ["flag"]}, "flag-uruguay": {"a": "flag: Uruguay", "b": "1F1FA-1F1FE", "j": ["flag"]}, "flag-uzbekistan": {"a": "flag: Uzbekistan", "b": "1F1FA-1F1FF", "j": ["flag"]}, "flag-vatican-city": {"a": "flag: Vatican City", "b": "1F1FB-1F1E6", "j": ["flag"]}, "flag-st-vincent--grenadines": {"a": "flag: St. Vincent & Grenadines", "b": "1F1FB-1F1E8", "j": ["flag"]}, "flag-venezuela": {"a": "flag: Venezuela", "b": "1F1FB-1F1EA", "j": ["flag"]}, "flag-british-virgin-islands": {"a": "flag: British Virgin Islands", "b": "1F1FB-1F1EC", "j": ["flag"]}, "flag-us-virgin-islands": {"a": "flag: U.S. Virgin Islands", "b": "1F1FB-1F1EE", "j": ["flag"]}, "flag-vietnam": {"a": "flag: Vietnam", "b": "1F1FB-1F1F3", "j": ["flag"]}, "flag-vanuatu": {"a": "flag: Vanuatu", "b": "1F1FB-1F1FA", "j": ["flag"]}, "flag-wallis--futuna": {"a": "flag: Wallis & Futuna", "b": "1F1FC-1F1EB", "j": ["flag"]}, "flag-samoa": {"a": "flag: Samoa", "b": "1F1FC-1F1F8", "j": ["flag"]}, "flag-kosovo": {"a": "flag: Kosovo", "b": "1F1FD-1F1F0", "j": ["flag"]}, "flag-yemen": {"a": "flag: Yemen", "b": "1F1FE-1F1EA", "j": ["flag"]}, "flag-mayotte": {"a": "flag: Mayotte", "b": "1F1FE-1F1F9", "j": ["flag"]}, "flag-south-africa": {"a": "flag: South Africa", "b": "1F1FF-1F1E6", "j": ["flag"]}, "flag-zambia": {"a": "flag: Zambia", "b": "1F1FF-1F1F2", "j": ["flag"]}, "flag-zimbabwe": {"a": "flag: Zimbabwe", "b": "1F1FF-1F1FC", "j": ["flag"]}, "flag-england": {"a": "flag: England", "b": "1F3F4-E0067-E0062-E0065-E006E-E0067-E007F", "j": ["flag"]}, "flag-scotland": {"a": "flag: Scotland", "b": "1F3F4-E0067-E0062-E0073-E0063-E0074-E007F", "j": ["flag"]}, "flag-wales": {"a": "flag: Wales", "b": "1F3F4-E0067-E0062-E0077-E006C-E0073-E007F", "j": ["flag"]}}, "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": ["grin", "joy", ":D", "face", "smile", "happy"]}, "grinning-face-with-big-eyes": {"a": "grinning face with big eyes", "b": "1F603", "j": ["joy", ":)", ":D", "open", "face", "smile", "mouth", "haha", "happy", "funny"]}, "grinning-face-with-smiling-eyes": {"a": "grinning face with smiling eyes", "b": "1F604", "j": ["like", "joy", ":)", ":D", "open", "face", "smile", "eye", "mouth", "haha", "laugh", "happy", "funny"]}, "beaming-face-with-smiling-eyes": {"a": "beaming face with smiling eyes", "b": "1F601", "j": ["grin", "joy", "face", "smile", "eye", "kawaii", "happy"]}, "grinning-squinting-face": {"a": "grinning squinting face", "b": "1F606", "j": ["joy", "XD", "face", "smile", "satisfied", "mouth", "laugh", "haha", "happy", "lol", "glad"]}, "grinning-face-with-sweat": {"a": "grinning face with sweat", "b": "1F605", "j": ["sweat", "relief", "hot", "cold", "open", "face", "smile", "laugh", "happy"]}, "rolling-on-the-floor-laughing": {"a": "rolling on the floor laughing", "b": "1F923", "j": ["lol", "rolling", "rotfl", "laughing", "face", "laugh", "haha", "floor", "rofl"]}, "face-with-tears-of-joy": {"a": "face with tears of joy", "b": "1F602", "j": ["cry", "tears", "joy", "weep", "face", "laugh", "haha", "happy", "happytears", "tear"]}, "slightly-smiling-face": {"a": "slightly smiling face", "b": "1F642", "j": ["smile", "face"]}, "upsidedown-face": {"a": "upside-down face", "b": "1F643", "j": ["face", "smile", "upside_down_face", "silly", "upside-down", "flipped"]}, "winking-face": {"a": "winking face", "b": "1F609", "j": [";)", "secret", "mischievous", "face", "smile", "eye", "happy", "wink"]}, "smiling-face-with-smiling-eyes": {"a": "smiling face with smiling eyes", "b": "1F60A", "j": ["embarrassed", "joy", "flushed", "blush", "face", "smile", "eye", "happy", "shy", "crush"]}, "smiling-face-with-halo": {"a": "smiling face with halo", "b": "1F607", "j": ["heaven", "innocent", "face", "fantasy", "angel", "halo"]}, "smiling-face-with-hearts": {"a": "smiling face with hearts", "b": "1F970", "j": ["love", "like", "infatuation", "face", "valentines", "hearts", "in love", "adore", "affection", "crush"]}, "smiling-face-with-hearteyes": {"a": "smiling face with heart-eyes", "b": "1F60D", "j": ["love", "like", "heart", "infatuation", "face", "smile", "eye", "valentines", "affection", "smiling_face_with_heart_eyes", "crush"]}, "starstruck": {"a": "star-struck", "b": "1F929", "j": ["grinning", "star", "face", "star_struck", "starry", "smile", "starry-eyed", "eyes"]}, "face-blowing-a-kiss": {"a": "face blowing a kiss", "b": "1F618", "j": ["love", "like", "infatuation", "face", "valentines", "affection", "kiss"]}, "kissing-face": {"a": "kissing face", "b": "1F617", "j": ["love", "like", "infatuation", "3", "face", "valentines", "kiss"]}, "smiling-face": {"a": "smiling face", "b": "263A", "j": ["relaxed", "happiness", "massage", "blush", "face", "outlined", "smile"]}, "kissing-face-with-closed-eyes": {"a": "kissing face with closed eyes", "b": "1F61A", "j": ["love", "like", "infatuation", "face", "eye", "valentines", "closed", "affection", "kiss"]}, "kissing-face-with-smiling-eyes": {"a": "kissing face with smiling eyes", "b": "1F619", "j": ["infatuation", "face", "smile", "valentines", "eye", "affection", "kiss"]}, "smiling-face-with-tear": {"a": "smiling face with tear", "b": "1F972", "j": ["grateful", "relieved", "proud", "pretend", "touched", "smiling", "cry", "sad", "tear"]}, "face-savoring-food": {"a": "face savoring food", "b": "1F60B", "j": ["tongue", "joy", "yummy", "silly", "face", "savouring", "smile", "happy", "nom", "delicious", "yum"]}, "face-with-tongue": {"a": "face with tongue", "b": "1F61B", "j": ["tongue", "mischievous", "playful", "face", "smile", "prank", "childish"]}, "winking-face-with-tongue": {"a": "winking face with tongue", "b": "1F61C", "j": ["tongue", "playful", "mischievous", "face", "smile", "eye", "joke", "wink", "prank", "childish"]}, "zany-face": {"a": "zany face", "b": "1F92A", "j": ["large", "crazy", "face", "goofy", "eye", "small"]}, "squinting-face-with-tongue": {"a": "squinting face with tongue", "b": "1F61D", "j": ["tongue", "playful", "mischievous", "taste", "face", "horrible", "eye", "smile", "prank"]}, "moneymouth-face": {"a": "money-mouth face", "b": "1F911", "j": ["rich", "face", "dollar", "mouth", "money_mouth_face", "money"]}, "hugging-face": {"a": "hugging face", "b": "1F917", "j": ["smile", "hugging", "face", "hug"]}, "face-with-hand-over-mouth": {"a": "face with hand over mouth", "b": "1F92D", "j": ["whoops", "face", "sudden realization", "shock", "surprise"]}, "shushing-face": {"a": "shushing face", "b": "1F92B", "j": ["quiet", "shush", "face", "shhh"]}, "thinking-face": {"a": "thinking face", "b": "1F914", "j": ["think", "consider", "hmmm", "thinking", "face"]}, "zippermouth-face": {"a": "zipper-mouth face", "b": "1F910", "j": ["secret", "zipper_mouth_face", "face", "sealed", "mouth", "zipper"]}, "face-with-raised-eyebrow": {"a": "face with raised eyebrow", "b": "1F928", "j": ["disbelief", "skeptic", "disapproval", "face", "mild surprise", "scepticism", "distrust", "surprise"]}, "neutral-face": {"a": "neutral face", "b": "1F610", "j": ["deadpan", "neutral", "face", "meh", "indifference", ":|"]}, "expressionless-face": {"a": "expressionless face", "b": "1F611", "j": ["inexpressive", "indifferent", "deadpan", "-_-", "face", "meh", "expressionless", "unexpressive"]}, "face-without-mouth": {"a": "face without mouth", "b": "1F636", "j": ["hellokitty", "face", "quiet", "mouth", "silent"]}, "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": ["smug", "face", "smile", "sarcasm", "mean", "prank", "smirk"]}, "unamused-face": {"a": "unamused face", "b": "1F612", "j": ["side_eye", "bored", "unamused", "unhappy", "straight face", "face", "skeptical", "dubious", "sarcasm", "indifference", "serious", "unimpressed"]}, "face-with-rolling-eyes": {"a": "face with rolling eyes", "b": "1F644", "j": ["rolling", "frustrated", "face", "eyeroll", "eyes"]}, "grimacing-face": {"a": "grimacing face", "b": "1F62C", "j": ["grimace", "teeth", "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": ["pinocchio", "face", "lie"]}, "relieved-face": {"a": "relieved face", "b": "1F60C", "j": ["relaxed", "massage", "happiness", "relieved", "phew", "face"]}, "pensive-face": {"a": "pensive face", "b": "1F614", "j": ["depressed", "upset", "pensive", "face", "sad", "dejected"]}, "sleepy-face": {"a": "sleepy face", "b": "1F62A", "j": ["sleep", "tired", "face", "nap", "rest"]}, "drooling-face": {"a": "drooling face", "b": "1F924", "j": ["face", "drooling"]}, "sleeping-face": {"a": "sleeping face", "b": "1F634", "j": ["night", "sleep", "tired", "zzz", "face", "sleepy"]}, "face-with-medical-mask": {"a": "face with medical mask", "b": "1F637", "j": ["ill", "mask", "cold", "face", "doctor", "disease", "sick"]}, "face-with-thermometer": {"a": "face with thermometer", "b": "1F912", "j": ["ill", "thermometer", "fever", "cold", "face", "temperature", "sick"]}, "face-with-headbandage": {"a": "face with head-bandage", "b": "1F915", "j": ["face_with_head_bandage", "face", "injury", "clumsy", "hurt", "injured", "bandage"]}, "nauseated-face": {"a": "nauseated face", "b": "1F922", "j": ["ill", "throw up", "gross", "face", "green", "sick", "vomit", "nauseated"]}, "face-vomiting": {"a": "face vomiting", "b": "1F92E", "j": ["sick", "vomit", "face", "puke"]}, "sneezing-face": {"a": "sneezing face", "b": "1F927", "j": ["gesundheit", "face", "sick", "sneeze", "allergy"]}, "hot-face": {"a": "hot face", "b": "1F975", "j": ["sweating", "heat", "hot", "red-faced", "feverish", "face", "red", "heat stroke"]}, "cold-face": {"a": "cold face", "b": "1F976", "j": ["blue", "blue-faced", "frostbite", "freezing", "cold", "face", "icicles", "frozen"]}, "woozy-face": {"a": "woozy face", "b": "1F974", "j": ["face", "intoxicated", "dizzy", "wavy mouth", "tipsy", "uneven eyes", "wavy"]}, "knockedout-face": {"a": "knocked-out face", "b": "1F635", "j": ["knocked out", "dizzy_face", "unconscious", "face", "spent", "dizzy", "dead", "xox"]}, "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": ["face", "shocked", "mind blown", "blown", "mind"]}, "cowboy-hat-face": {"a": "cowboy hat face", "b": "1F920", "j": ["hat", "cowboy", "face", "cowgirl"]}, "partying-face": {"a": "partying face", "b": "1F973", "j": ["face", "hat", "celebration", "horn", "party", "woohoo"]}, "disguised-face": {"a": "disguised face", "b": "1F978", "j": ["moustache", "glasses", "face", "brows", "disguise", "pretent", "nose", "incognito"]}, "smiling-face-with-sunglasses": {"a": "smiling face with sunglasses", "b": "1F60E", "j": ["sun", "cool", "sunglasses", "bright", "face", "sunglass", "smile", "summer", "beach"]}, "nerd-face": {"a": "nerd face", "b": "1F913", "j": ["nerdy", "geek", "nerd", "face", "dork"]}, "face-with-monocle": {"a": "face with monocle", "b": "1F9D0", "j": ["wealthy", "face", "stuffy"]}, "confused-face": {"a": "confused face", "b": "1F615", "j": ["hmmm", "face", "huh", "meh", "weird", "indifference", "confused", ":/"]}, "worried-face": {"a": "worried face", "b": "1F61F", "j": ["nervous", ":(", "face", "concern", "worried"]}, "slightly-frowning-face": {"a": "slightly frowning face", "b": "1F641", "j": ["frowning", "upset", "disappointed", "frown", "face", "sad"]}, "frowning-face": {"a": "frowning face", "b": "2639", "j": ["frown", "upset", "sad", "face"]}, "face-with-open-mouth": {"a": "face with open mouth", "b": "1F62E", "j": [":O", "open", "face", "wow", "impressed", "mouth", "whoa", "sympathy", "surprise"]}, "hushed-face": {"a": "hushed face", "b": "1F62F", "j": ["shh", "stunned", "face", "hushed", "surprised", "woo"]}, "astonished-face": {"a": "astonished face", "b": "1F632", "j": ["face", "shocked", "surprised", "astonished", "totally", "poisoned", "xox"]}, "flushed-face": {"a": "flushed face", "b": "1F633", "j": ["flattered", "blush", "face", "dazed", "flushed", "shy"]}, "pleading-face": {"a": "pleading face", "b": "1F97A", "j": ["puppy eyes", "begging", "face", "mercy"]}, "frowning-face-with-open-mouth": {"a": "frowning face with open mouth", "b": "1F626", "j": ["frown", "open", "face", "mouth", "what", "aw"]}, "anguished-face": {"a": "anguished face", "b": "1F627", "j": ["stunned", "nervous", "face", "anguished"]}, "fearful-face": {"a": "fearful face", "b": "1F628", "j": ["oops", "nervous", "face", "scared", "huh", "fear", "terrified", "fearful"]}, "anxious-face-with-sweat": {"a": "anxious face with sweat", "b": "1F630", "j": ["blue", "sweat", "nervous", "cold", "face", "rushed"]}, "sad-but-relieved-face": {"a": "sad but relieved face", "b": "1F625", "j": ["disappointed", "nervous", "sweat", "relieved", "whew", "phew", "face"]}, "crying-face": {"a": "crying face", "b": "1F622", "j": ["depressed", "upset", "tears", "face", ":'(", "cry", "sad", "tear"]}, "loudly-crying-face": {"a": "loudly crying face", "b": "1F62D", "j": ["depressed", "upset", "tears", "face", "cry", "sad", "tear", "sob"]}, "face-screaming-in-fear": {"a": "face screaming in fear", "b": "1F631", "j": ["munch", "face", "omg", "scared", "scream", "fear"]}, "confounded-face": {"a": "confounded face", "b": "1F616", "j": ["confounded", "oops", ":S", "sick", "unwell", "face", "confused"]}, "persevering-face": {"a": "persevering face", "b": "1F623", "j": ["oops", "upset", "persevere", "no", "face", "sick"]}, "disappointed-face": {"a": "disappointed face", "b": "1F61E", "j": ["depressed", "upset", "disappointed", ":(", "face", "sad"]}, "downcast-face-with-sweat": {"a": "downcast face with sweat", "b": "1F613", "j": ["sweat", "tired", "hot", "cold", "face", "exercise", "sad"]}, "weary-face": {"a": "weary face", "b": "1F629", "j": ["upset", "tired", "frustrated", "face", "sleepy", "sad", "weary"]}, "tired-face": {"a": "tired face", "b": "1F62B", "j": ["upset", "whine", "tired", "frustrated", "face", "sick"]}, "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": ["triumph", "gas", "pride", "phew", "face", "proud", "won"]}, "pouting-face": {"a": "pouting face", "b": "1F621", "j": ["pouting", "rage", "despise", "hate", "face", "red", "angry", "mad"]}, "angry-face": {"a": "angry face", "b": "1F620", "j": ["annoyed", "anger", "frustrated", "face", "angry", "mad"]}, "face-with-symbols-on-mouth": {"a": "face with symbols on mouth", "b": "1F92C", "j": ["cursing", "swearing", "face", "expletive", "cussing", "profanity"]}, "smiling-face-with-horns": {"a": "smiling face with horns", "b": "1F608", "j": ["face", "fairy tale", "smile", "fantasy", "devil", "horns"]}, "angry-face-with-horns": {"a": "angry face with horns", "b": "1F47F", "j": ["face", "devil", "fantasy", "demon", "imp", "horns", "angry"]}, "skull": {"a": "skull", "b": "1F480", "j": ["death", "skeleton", "face", "creepy", "fairy tale", "dead", "monster"]}, "skull-and-crossbones": {"a": "skull and crossbones", "b": "2620", "j": ["danger", "death", "pirate", "skull", "scary", "face", "poison", "evil", "crossbones", "deadly", "monster"]}, "pile-of-poo": {"a": "pile of poo", "b": "1F4A9", "j": ["dung", "poop", "hankey", "face", "shitface", "fail", "turd", "shit", "poo", "monster"]}, "clown-face": {"a": "clown face", "b": "1F921", "j": ["clown", "face"]}, "ogre": {"a": "ogre", "b": "1F479", "j": ["mask", "scary", "japanese", "face", "creepy", "fairy tale", "halloween", "red", "fantasy", "creature", "troll", "devil", "demon", "monster"]}, "goblin": {"a": "goblin", "b": "1F47A", "j": ["mask", "scary", "japanese", "face", "creepy", "fairy tale", "red", "fantasy", "creature", "evil", "monster"]}, "ghost": {"a": "ghost", "b": "1F47B", "j": ["scary", "face", "fairy tale", "halloween", "spooky", "fantasy", "creature", "monster"]}, "alien": {"a": "alien", "b": "1F47D", "j": ["extraterrestrial", "paul", "face", "fantasy", "creature", "ufo", "weird", "outer_space", "UFO"]}, "alien-monster": {"a": "alien monster", "b": "1F47E", "j": ["extraterrestrial", "play", "face", "alien", "game", "creature", "ufo", "arcade", "monster"]}, "robot": {"a": "robot", "b": "1F916", "j": ["computer", "face", "bot", "machine", "monster"]}, "grinning-cat": {"a": "grinning cat", "b": "1F63A", "j": ["grinning", "cats", "open", "face", "smile", "mouth", "happy", "cat", "animal"]}, "grinning-cat-with-smiling-eyes": {"a": "grinning cat with smiling eyes", "b": "1F638", "j": ["animal", "cats", "grin", "face", "smile", "eye", "cat"]}, "cat-with-tears-of-joy": {"a": "cat with tears of joy", "b": "1F639", "j": ["animal", "tears", "cats", "joy", "face", "haha", "happy", "cat", "tear"]}, "smiling-cat-with-hearteyes": {"a": "smiling cat with heart-eyes", "b": "1F63B", "j": ["animal", "love", "like", "cats", "heart", "face", "smile", "eye", "smiling_cat_with_heart_eyes", "valentines", "affection", "cat"]}, "cat-with-wry-smile": {"a": "cat with wry smile", "b": "1F63C", "j": ["animal", "ironic", "cats", "face", "smile", "wry", "cat", "smirk"]}, "kissing-cat": {"a": "kissing cat", "b": "1F63D", "j": ["animal", "cats", "face", "eye", "kiss", "cat"]}, "weary-cat": {"a": "weary cat", "b": "1F640", "j": ["cats", "weary", "munch", "face", "oh", "surprised", "scared", "scream", "cat", "animal"]}, "crying-cat": {"a": "crying cat", "b": "1F63F", "j": ["upset", "animal", "tears", "cats", "weep", "face", "cry", "sad", "cat", "tear"]}, "pouting-cat": {"a": "pouting cat", "b": "1F63E", "j": ["pouting", "cats", "face", "cat", "animal"]}, "seenoevil-monkey": {"a": "see-no-evil monkey", "b": "1F648", "j": ["monkey", "forbidden", "face", "nature", "haha", "evil", "see", "animal", "see_no_evil_monkey"]}, "hearnoevil-monkey": {"a": "hear-no-evil monkey", "b": "1F649", "j": ["monkey", "forbidden", "animal", "hear_no_evil_monkey", "face", "nature", "evil", "hear"]}, "speaknoevil-monkey": {"a": "speak-no-evil monkey", "b": "1F64A", "j": ["monkey", "forbidden", "face", "speak_no_evil_monkey", "nature", "speak", "omg", "evil", "animal"]}, "kiss-mark": {"a": "kiss mark", "b": "1F48B", "j": ["love", "like", "face", "lips", "valentines", "affection", "kiss"]}, "love-letter": {"a": "love letter", "b": "1F48C", "j": ["envelope", "love", "like", "letter", "heart", "email", "valentines", "mail", "affection"]}, "heart-with-arrow": {"a": "heart with arrow", "b": "1F498", "j": ["arrow", "love", "like", "heart", "cupid", "valentines", "affection"]}, "heart-with-ribbon": {"a": "heart with ribbon", "b": "1F49D", "j": ["valentines", "valentine", "love", "ribbon"]}, "sparkling-heart": {"a": "sparkling heart", "b": "1F496", "j": ["love", "like", "excited", "valentines", "sparkle", "affection"]}, "growing-heart": {"a": "growing heart", "b": "1F497", "j": ["pulse", "nervous", "like", "love", "excited", "pink", "valentines", "growing", "affection"]}, "beating-heart": {"a": "beating heart", "b": "1F493", "j": ["love", "like", "heart", "beating", "pink", "heartbeat", "valentines", "pulsating", "affection"]}, "revolving-hearts": {"a": "revolving hearts", "b": "1F49E", "j": ["revolving", "like", "love", "valentines", "affection"]}, "two-hearts": {"a": "two hearts", "b": "1F495", "j": ["love", "like", "heart", "valentines", "affection"]}, "heart-decoration": {"a": "heart decoration", "b": "1F49F", "j": ["heart", "love", "like", "purple-square"]}, "heart-exclamation": {"a": "heart exclamation", "b": "2763", "j": ["punctuation", "love", "mark", "decoration", "exclamation"]}, "broken-heart": {"a": "broken heart", "b": "1F494", "j": ["heartbreak", "heart", "broken", "sorry", "break", "sad"]}, "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", "valentines", "love", "like"]}, "orange-heart": {"a": "orange heart", "b": "1F9E1", "j": ["like", "love", "orange", "valentines", "affection"]}, "yellow-heart": {"a": "yellow heart", "b": "1F49B", "j": ["love", "like", "valentines", "yellow", "affection"]}, "green-heart": {"a": "green heart", "b": "1F49A", "j": ["love", "like", "valentines", "green", "affection"]}, "blue-heart": {"a": "blue heart", "b": "1F499", "j": ["blue", "like", "love", "valentines", "affection"]}, "purple-heart": {"a": "purple heart", "b": "1F49C", "j": ["love", "like", "valentines", "affection", "purple"]}, "brown-heart": {"a": "brown heart", "b": "1F90E", "j": ["heart", "brown", "coffee"]}, "black-heart": {"a": "black heart", "b": "1F5A4", "j": ["wicked", "black", "evil"]}, "white-heart": {"a": "white heart", "b": "1F90D", "j": ["heart", "white", "pure"]}, "hundred-points": {"a": "hundred points", "b": "1F4AF", "j": ["test", "full", "score", "quiz", "numbers", "hundred", "100", "perfect", "pass", "century", "exam"]}, "anger-symbol": {"a": "anger symbol", "b": "1F4A2", "j": ["comic", "mad", "angry"]}, "collision": {"a": "collision", "b": "1F4A5", "j": ["boom", "explode", "explosion", "comic", "bomb", "blown"]}, "dizzy": {"a": "dizzy", "b": "1F4AB", "j": ["magic", "star", "comic", "sparkle", "shoot"]}, "sweat-droplets": {"a": "sweat droplets", "b": "1F4A6", "j": ["oops", "sweat", "drip", "water", "comic", "splashing"]}, "dashing-away": {"a": "dashing away", "b": "1F4A8", "j": ["smoke", "fart", "dash", "puff", "air", "fast", "comic", "running", "shoo", "wind"]}, "hole": {"a": "hole", "b": "1F573", "j": ["embarrassing"]}, "bomb": {"a": "bomb", "b": "1F4A3", "j": ["boom", "explode", "explosion", "comic", "terrorism"]}, "speech-balloon": {"a": "speech balloon", "b": "1F4AC", "j": ["bubble", "balloon", "words", "message", "talk", "chatting", "comic", "speech", "dialog"]}, "eye-in-speech-bubble": {"a": "eye in speech bubble", "b": "1F441-FE0F-200D-1F5E8-FE0F", "j": ["info", "witness", "speech bubble", "eye"]}, "left-speech-bubble": {"a": "left speech bubble", "b": "1F5E8", "j": ["message", "talk", "words", "chatting", "speech", "dialog"]}, "right-anger-bubble": {"a": "right anger bubble", "b": "1F5EF", "j": ["bubble", "balloon", "thinking", "caption", "speech", "angry", "mad"]}, "thought-balloon": {"a": "thought balloon", "b": "1F4AD", "j": ["bubble", "dream", "balloon", "thinking", "comic", "cloud", "speech", "thought"]}, "zzz": {"a": "zzz", "b": "1F4A4", "j": ["sleep", "dream", "tired", "comic", "sleepy"]}, "waving-hand": {"a": "waving hand", "b": "1F44B", "j": ["waving", "wave", "farewell", "solong", "gesture", "hand", "hands", "hello", "goodbye", "hi", "palm"]}, "raised-back-of-hand": {"a": "raised back of hand", "b": "1F91A", "j": ["backhand", "fingers", "raised"]}, "hand-with-fingers-splayed": {"a": "hand with fingers splayed", "b": "1F590", "j": ["finger", "fingers", "hand", "splayed", "palm"]}, "raised-hand": {"a": "raised hand", "b": "270B", "j": ["fingers", "stop", "hand", "high 5", "high five", "ban", "palm", "highfive"]}, "vulcan-salute": {"a": "vulcan salute", "b": "1F596", "j": ["finger", "fingers", "hand", "vulcan", "star trek", "spock"]}, "ok-hand": {"a": "OK hand", "b": "1F44C", "j": ["OK", "limbs", "fingers", "hand", "okay", "perfect", "ok"]}, "pinched-fingers": {"a": "pinched fingers", "b": "1F90C", "j": ["pinched", "fingers", "hand gesture", "size", "tiny", "sarcastic", "small", "interrogation"]}, "pinching-hand": {"a": "pinching hand", "b": "1F90F", "j": ["small amount", "size", "small", "tiny"]}, "victory-hand": {"a": "victory hand", "b": "270C", "j": ["victory", "fingers", "hand", "ohyeah", "two", "v", "peace"]}, "crossed-fingers": {"a": "crossed fingers", "b": "1F91E", "j": ["cross", "finger", "lucky", "good", "hand", "luck"]}, "loveyou-gesture": {"a": "love-you gesture", "b": "1F91F", "j": ["ILY", "fingers", "gesture", "hand", "love_you_gesture"]}, "sign-of-the-horns": {"a": "sign of the horns", "b": "1F918", "j": ["finger", "rock_on", "fingers", "rock-on", "hand", "sign_of_horns", "horns", "evil_eye"]}, "call-me-hand": {"a": "call me hand", "b": "1F919", "j": ["gesture", "hand", "call", "hands"]}, "backhand-index-pointing-left": {"a": "backhand index pointing left", "b": "1F448", "j": ["finger", "fingers", "point", "hand", "left", "backhand", "direction", "index"]}, "backhand-index-pointing-right": {"a": "backhand index pointing right", "b": "1F449", "j": ["finger", "right", "fingers", "point", "hand", "backhand", "direction", "index"]}, "backhand-index-pointing-up": {"a": "backhand index pointing up", "b": "1F446", "j": ["finger", "fingers", "point", "hand", "backhand", "direction", "up"]}, "middle-finger": {"a": "middle finger", "b": "1F595", "j": ["flipping", "finger", "middle", "fingers", "hand", "rude"]}, "backhand-index-pointing-down": {"a": "backhand index pointing down", "b": "1F447", "j": ["finger", "fingers", "point", "down", "hand", "backhand", "direction"]}, "index-pointing-up": {"a": "index pointing up", "b": "261D", "j": ["finger", "fingers", "point", "hand", "direction", "up", "index"]}, "thumbs-up": {"a": "thumbs up", "b": "1F44D", "j": ["+1", "yes", "awesome", "like", "cool", "thumb", "thumbsup", "hand", "good", "agree", "accept", "up"]}, "thumbs-down": {"a": "thumbs down", "b": "1F44E", "j": ["-1", "no", "thumb", "down", "hand", "dislike", "thumbsdown"]}, "raised-fist": {"a": "raised fist", "b": "270A", "j": ["fingers", "clenched", "fist", "hand", "grasp", "punch"]}, "oncoming-fist": {"a": "oncoming fist", "b": "1F44A", "j": ["violence", "clenched", "fist", "hit", "hand", "attack", "punch", "angry"]}, "leftfacing-fist": {"a": "left-facing fist", "b": "1F91B", "j": ["left_facing_fist", "leftwards", "fist", "hand", "fistbump"]}, "rightfacing-fist": {"a": "right-facing fist", "b": "1F91C", "j": ["fist", "hand", "right_facing_fist", "rightwards", "fistbump"]}, "clapping-hands": {"a": "clapping hands", "b": "1F44F", "j": ["applause", "hand", "yay", "hands", "clap", "congrats", "praise"]}, "raising-hands": {"a": "raising hands", "b": "1F64C", "j": ["gesture", "hand", "hands", "celebration", "raised", "hooray", "yea"]}, "open-hands": {"a": "open hands", "b": "1F450", "j": ["fingers", "hand", "open", "hands", "butterfly"]}, "palms-up-together": {"a": "palms up together", "b": "1F932", "j": ["gesture", "cupped", "hands", "cupped hands", "prayer"]}, "handshake": {"a": "handshake", "b": "1F91D", "j": ["shake", "meeting", "hand", "agreement"]}, "folded-hands": {"a": "folded hands", "b": "1F64F", "j": ["hand", "high 5", "please", "high five", "thanks", "wish", "pray", "ask", "namaste", "hope", "highfive"]}, "writing-hand": {"a": "writing hand", "b": "270D", "j": ["lower_left_ballpoint_pen", "compose", "hand", "stationery", "write"]}, "nail-polish": {"a": "nail polish", "b": "1F485", "j": ["care", "finger", "manicure", "fashion", "polish", "beauty", "nail", "cosmetics"]}, "selfie": {"a": "selfie", "b": "1F933", "j": ["camera", "phone"]}, "flexed-biceps": {"a": "flexed biceps", "b": "1F4AA", "j": ["strong", "hand", "arm", "comic", "flex", "biceps", "summer", "muscle"]}, "mechanical-arm": {"a": "mechanical arm", "b": "1F9BE", "j": ["prosthetic", "accessibility"]}, "mechanical-leg": {"a": "mechanical leg", "b": "1F9BF", "j": ["prosthetic", "accessibility"]}, "leg": {"a": "leg", "b": "1F9B5", "j": ["limb", "kick"]}, "foot": {"a": "foot", "b": "1F9B6", "j": ["kick", "stomp"]}, "ear": {"a": "ear", "b": "1F442", "j": ["sound", "face", "body", "hear", "listen"]}, "ear-with-hearing-aid": {"a": "ear with hearing aid", "b": "1F9BB", "j": ["hard of hearing", "accessibility"]}, "nose": {"a": "nose", "b": "1F443", "j": ["smell", "sniff", "body"]}, "brain": {"a": "brain", "b": "1F9E0", "j": ["smart", "intelligent"]}, "anatomical-heart": {"a": "anatomical heart", "b": "1FAC0", "j": ["pulse", "heart", "cardiology", "organ", "heartbeat", "health", "anatomical"]}, "lungs": {"a": "lungs", "b": "1FAC1", "j": ["inhalation", "breath", "exhalation", "organ", "respiration", "breathe"]}, "tooth": {"a": "tooth", "b": "1F9B7", "j": ["dentist", "teeth"]}, "bone": {"a": "bone", "b": "1F9B4", "j": ["skeleton"]}, "eyes": {"a": "eyes", "b": "1F440", "j": ["stalk", "peek", "face", "eye", "watch", "see", "look"]}, "eye": {"a": "eye", "b": "1F441", "j": ["stare", "face", "watch", "body", "see", "look"]}, "tongue": {"a": "tongue", "b": "1F445", "j": ["playful", "mouth", "body"]}, "mouth": {"a": "mouth", "b": "1F444", "j": ["lips", "kiss"]}, "baby": {"a": "baby", "b": "1F476", "j": ["toddler", "young", "boy", "girl", "child"]}, "child": {"a": "child", "b": "1F9D2", "j": ["young", "gender-neutral", "unspecified gender"]}, "boy": {"a": "boy", "b": "1F466", "j": ["guy", "man", "male", "young", "teenager"]}, "girl": {"a": "girl", "b": "1F467", "j": ["woman", "young", "zodiac", "Virgo", "teenager", "female"]}, "person": {"a": "person", "b": "1F9D1", "j": ["adult", "gender-neutral", "unspecified gender"]}, "person-blond-hair": {"a": "person: blond hair", "b": "1F471", "j": ["hairstyle", "blond-haired person", "blond", "hair"]}, "man": {"a": "man", "b": "1F468", "j": ["guy", "classy", "moustache", "father", "sir", "dad", "mustache", "adult"]}, "person-beard": {"a": "person: beard", "b": "1F9D4", "j": ["person", "bewhiskered", "man_beard", "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", "red hair", "man", "hairstyle"]}, "man-curly-hair": {"a": "man: curly hair", "b": "1F468-200D-1F9B1", "j": ["curly hair", "adult", "man", "hairstyle"]}, "man-white-hair": {"a": "man: white hair", "b": "1F468-200D-1F9B3", "j": ["man", "elder", "old", "adult", "white hair"]}, "man-bald": {"a": "man: bald", "b": "1F468-200D-1F9B2", "j": ["hairless", "adult", "man", "bald"]}, "woman": {"a": "woman", "b": "1F469", "j": ["girls", "lady", "adult", "female"]}, "woman-red-hair": {"a": "woman: red hair", "b": "1F469-200D-1F9B0", "j": ["woman", "adult", "red hair", "hairstyle"]}, "person-red-hair": {"a": "person: red hair", "b": "1F9D1-200D-1F9B0", "j": ["person", "unspecified gender", "hairstyle", "red hair", "adult", "gender-neutral"]}, "woman-curly-hair": {"a": "woman: curly hair", "b": "1F469-200D-1F9B1", "j": ["woman", "curly hair", "adult", "hairstyle"]}, "person-curly-hair": {"a": "person: curly hair", "b": "1F9D1-200D-1F9B1", "j": ["person", "unspecified gender", "hairstyle", "curly hair", "adult", "gender-neutral"]}, "woman-white-hair": {"a": "woman: white hair", "b": "1F469-200D-1F9B3", "j": ["woman", "elder", "old", "adult", "white hair"]}, "person-white-hair": {"a": "person: white hair", "b": "1F9D1-200D-1F9B3", "j": ["person", "unspecified gender", "elder", "white hair", "old", "adult", "gender-neutral"]}, "woman-bald": {"a": "woman: bald", "b": "1F469-200D-1F9B2", "j": ["hairless", "adult", "bald", "woman"]}, "person-bald": {"a": "person: bald", "b": "1F9D1-200D-1F9B2", "j": ["hairless", "person", "unspecified gender", "bald", "adult", "gender-neutral"]}, "woman-blond-hair": {"a": "woman: blond hair", "b": "1F471-200D-2640-FE0F", "j": ["person", "woman", "hair", "girl", "blond-haired woman", "blonde", "female"]}, "man-blond-hair": {"a": "man: blond hair", "b": "1F471-200D-2642-FE0F", "j": ["guy", "person", "man", "hair", "male", "boy", "blond-haired man", "blond", "blonde"]}, "older-person": {"a": "older person", "b": "1F9D3", "j": ["unspecified gender", "elder", "senior", "human", "old", "adult", "gender-neutral"]}, "old-man": {"a": "old man", "b": "1F474", "j": ["man", "male", "elder", "human", "senior", "old", "men", "adult"]}, "old-woman": {"a": "old woman", "b": "1F475", "j": ["woman", "elder", "human", "lady", "senior", "old", "adult", "women", "female"]}, "person-frowning": {"a": "person frowning", "b": "1F64D", "j": ["frown", "gesture", "worried"]}, "man-frowning": {"a": "man frowning", "b": "1F64D-200D-2642-FE0F", "j": ["frowning", "depressed", "man", "male", "gesture", "boy", "unhappy", "sad", "discouraged"]}, "woman-frowning": {"a": "woman frowning", "b": "1F64D-200D-2640-FE0F", "j": ["frowning", "depressed", "woman", "gesture", "girl", "unhappy", "sad", "discouraged", "female"]}, "person-pouting": {"a": "person pouting", "b": "1F64E", "j": ["pouting", "upset", "gesture"]}, "man-pouting": {"a": "man pouting", "b": "1F64E-200D-2642-FE0F", "j": ["pouting", "man", "male", "gesture", "boy"]}, "woman-pouting": {"a": "woman pouting", "b": "1F64E-200D-2640-FE0F", "j": ["pouting", "woman", "gesture", "girl", "female"]}, "person-gesturing-no": {"a": "person gesturing NO", "b": "1F645", "j": ["decline", "forbidden", "gesture", "prohibited", "hand"]}, "man-gesturing-no": {"a": "man gesturing NO", "b": "1F645-200D-2642-FE0F", "j": ["forbidden", "man", "male", "gesture", "prohibited", "hand", "boy", "nope"]}, "woman-gesturing-no": {"a": "woman gesturing NO", "b": "1F645-200D-2640-FE0F", "j": ["forbidden", "woman", "gesture", "prohibited", "hand", "girl", "female", "nope"]}, "person-gesturing-ok": {"a": "person gesturing OK", "b": "1F646", "j": ["OK", "hand", "gesture", "agree"]}, "man-gesturing-ok": {"a": "man gesturing OK", "b": "1F646-200D-2642-FE0F", "j": ["blue", "OK", "man", "male", "human", "gesture", "boy", "hand", "men"]}, "woman-gesturing-ok": {"a": "woman gesturing OK", "b": "1F646-200D-2640-FE0F", "j": ["OK", "woman", "human", "gesture", "girl", "hand", "pink", "women", "female"]}, "person-tipping-hand": {"a": "person tipping hand", "b": "1F481", "j": ["tipping", "hand", "sassy", "help", "information"]}, "man-tipping-hand": {"a": "man tipping hand", "b": "1F481-200D-2642-FE0F", "j": ["man", "male", "human", "boy", "sassy", "tipping hand", "information"]}, "woman-tipping-hand": {"a": "woman tipping hand", "b": "1F481-200D-2640-FE0F", "j": ["woman", "human", "girl", "sassy", "tipping hand", "information", "female"]}, "person-raising-hand": {"a": "person raising hand", "b": "1F64B", "j": ["gesture", "hand", "raised", "happy", "question"]}, "man-raising-hand": {"a": "man raising hand", "b": "1F64B-200D-2642-FE0F", "j": ["raising hand", "man", "male", "gesture", "boy"]}, "woman-raising-hand": {"a": "woman raising hand", "b": "1F64B-200D-2640-FE0F", "j": ["raising hand", "woman", "gesture", "girl", "female"]}, "deaf-person": {"a": "deaf person", "b": "1F9CF", "j": ["ear", "deaf", "accessibility", "hear"]}, "deaf-man": {"a": "deaf man", "b": "1F9CF-200D-2642-FE0F", "j": ["accessibility", "deaf", "man"]}, "deaf-woman": {"a": "deaf woman", "b": "1F9CF-200D-2640-FE0F", "j": ["deaf", "accessibility", "woman"]}, "person-bowing": {"a": "person bowing", "b": "1F647", "j": ["gesture", "sorry", "apology", "respectiful", "bow"]}, "man-bowing": {"a": "man bowing", "b": "1F647-200D-2642-FE0F", "j": ["man", "male", "gesture", "boy", "bowing", "sorry", "favor", "apology"]}, "woman-bowing": {"a": "woman bowing", "b": "1F647-200D-2640-FE0F", "j": ["woman", "gesture", "girl", "bowing", "sorry", "favor", "apology", "female"]}, "person-facepalming": {"a": "person facepalming", "b": "1F926", "j": ["disappointed", "disbelief", "face", "exasperation", "palm"]}, "man-facepalming": {"a": "man facepalming", "b": "1F926-200D-2642-FE0F", "j": ["disbelief", "facepalm", "man", "male", "boy", "exasperation"]}, "woman-facepalming": {"a": "woman facepalming", "b": "1F926-200D-2640-FE0F", "j": ["disbelief", "facepalm", "woman", "girl", "exasperation", "female"]}, "person-shrugging": {"a": "person shrugging", "b": "1F937", "j": ["regardless", "indifference", "doubt", "shrug", "ignorance"]}, "man-shrugging": {"a": "man shrugging", "b": "1F937-200D-2642-FE0F", "j": ["indifferent", "man", "male", "boy", "doubt", "indifference", "confused", "shrug", "ignorance"]}, "woman-shrugging": {"a": "woman shrugging", "b": "1F937-200D-2640-FE0F", "j": ["indifferent", "woman", "girl", "doubt", "indifference", "confused", "female", "shrug", "ignorance"]}, "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", "human", "doctor", "healthcare", "therapist"]}, "woman-health-worker": {"a": "woman health worker", "b": "1F469-200D-2695-FE0F", "j": ["nurse", "woman", "human", "doctor", "healthcare", "therapist"]}, "student": {"a": "student", "b": "1F9D1-200D-1F393", "j": ["learn", "graduate"]}, "man-student": {"a": "man student", "b": "1F468-200D-1F393", "j": ["human", "graduate", "student", "man"]}, "woman-student": {"a": "woman student", "b": "1F469-200D-1F393", "j": ["human", "graduate", "student", "woman"]}, "teacher": {"a": "teacher", "b": "1F9D1-200D-1F3EB", "j": ["professor", "instructor"]}, "man-teacher": {"a": "man teacher", "b": "1F468-200D-1F3EB", "j": ["instructor", "man", "teacher", "human", "professor"]}, "woman-teacher": {"a": "woman teacher", "b": "1F469-200D-1F3EB", "j": ["instructor", "woman", "teacher", "human", "professor"]}, "judge": {"a": "judge", "b": "1F9D1-200D-2696-FE0F", "j": ["scales", "justice", "law"]}, "man-judge": {"a": "man judge", "b": "1F468-200D-2696-FE0F", "j": ["court", "man", "human", "scales", "judge", "justice"]}, "woman-judge": {"a": "woman judge", "b": "1F469-200D-2696-FE0F", "j": ["court", "woman", "human", "scales", "judge", "justice"]}, "farmer": {"a": "farmer", "b": "1F9D1-200D-1F33E", "j": ["rancher", "crops", "gardener"]}, "man-farmer": {"a": "man farmer", "b": "1F468-200D-1F33E", "j": ["farmer", "man", "human", "rancher", "gardener"]}, "woman-farmer": {"a": "woman farmer", "b": "1F469-200D-1F33E", "j": ["farmer", "woman", "human", "rancher", "gardener"]}, "cook": {"a": "cook", "b": "1F9D1-200D-1F373", "j": ["chef", "food", "culinary", "kitchen"]}, "man-cook": {"a": "man cook", "b": "1F468-200D-1F373", "j": ["human", "cook", "chef", "man"]}, "woman-cook": {"a": "woman cook", "b": "1F469-200D-1F373", "j": ["human", "cook", "chef", "woman"]}, "mechanic": {"a": "mechanic", "b": "1F9D1-200D-1F527", "j": ["worker", "electrician", "plumber", "technician", "tradesperson"]}, "man-mechanic": {"a": "man mechanic", "b": "1F468-200D-1F527", "j": ["man", "human", "electrician", "plumber", "tradesperson", "mechanic", "wrench"]}, "woman-mechanic": {"a": "woman mechanic", "b": "1F469-200D-1F527", "j": ["woman", "human", "electrician", "plumber", "tradesperson", "mechanic", "wrench"]}, "factory-worker": {"a": "factory worker", "b": "1F9D1-200D-1F3ED", "j": ["worker", "factory", "labor", "industrial", "assembly"]}, "man-factory-worker": {"a": "man factory worker", "b": "1F468-200D-1F3ED", "j": ["man", "worker", "human", "factory", "industrial", "assembly"]}, "woman-factory-worker": {"a": "woman factory worker", "b": "1F469-200D-1F3ED", "j": ["worker", "woman", "human", "factory", "industrial", "assembly"]}, "office-worker": {"a": "office worker", "b": "1F9D1-200D-1F4BC", "j": ["white-collar", "architect", "manager", "business"]}, "man-office-worker": {"a": "man office worker", "b": "1F468-200D-1F4BC", "j": ["white-collar", "architect", "man", "business", "manager", "human"]}, "woman-office-worker": {"a": "woman office worker", "b": "1F469-200D-1F4BC", "j": ["white-collar", "architect", "manager", "business", "woman", "human"]}, "scientist": {"a": "scientist", "b": "1F9D1-200D-1F52C", "j": ["chemistry", "biologist", "physicist", "engineer", "chemist"]}, "man-scientist": {"a": "man scientist", "b": "1F468-200D-1F52C", "j": ["man", "biologist", "human", "physicist", "scientist", "engineer", "chemist"]}, "woman-scientist": {"a": "woman scientist", "b": "1F469-200D-1F52C", "j": ["biologist", "woman", "human", "physicist", "scientist", "engineer", "chemist"]}, "technologist": {"a": "technologist", "b": "1F9D1-200D-1F4BB", "j": ["computer", "software", "coder", "developer", "inventor"]}, "man-technologist": {"a": "man technologist", "b": "1F468-200D-1F4BB", "j": ["programmer", "laptop", "software", "computer", "coder", "man", "human", "developer", "inventor", "engineer", "technologist"]}, "woman-technologist": {"a": "woman technologist", "b": "1F469-200D-1F4BB", "j": ["programmer", "laptop", "software", "computer", "coder", "woman", "human", "developer", "inventor", "engineer", "technologist"]}, "singer": {"a": "singer", "b": "1F9D1-200D-1F3A4", "j": ["song", "artist", "star", "rock", "actor", "entertainer", "performer"]}, "man-singer": {"a": "man singer", "b": "1F468-200D-1F3A4", "j": ["man", "human", "star", "rockstar", "rock", "singer", "actor", "entertainer"]}, "woman-singer": {"a": "woman singer", "b": "1F469-200D-1F3A4", "j": ["woman", "human", "star", "rockstar", "rock", "singer", "actor", "entertainer"]}, "artist": {"a": "artist", "b": "1F9D1-200D-1F3A8", "j": ["painting", "creativity", "palette", "draw"]}, "man-artist": {"a": "man artist", "b": "1F468-200D-1F3A8", "j": ["man", "artist", "human", "painter", "palette"]}, "woman-artist": {"a": "woman artist", "b": "1F469-200D-1F3A8", "j": ["woman", "artist", "human", "painter", "palette"]}, "pilot": {"a": "pilot", "b": "1F9D1-200D-2708-FE0F", "j": ["airplane", "plane", "fly"]}, "man-pilot": {"a": "man pilot", "b": "1F468-200D-2708-FE0F", "j": ["man", "human", "plane", "pilot", "aviator"]}, "woman-pilot": {"a": "woman pilot", "b": "1F469-200D-2708-FE0F", "j": ["woman", "human", "plane", "pilot", "aviator"]}, "astronaut": {"a": "astronaut", "b": "1F9D1-200D-1F680", "j": ["outerspace", "rocket"]}, "man-astronaut": {"a": "man astronaut", "b": "1F468-200D-1F680", "j": ["man", "rocket", "human", "astronaut", "space"]}, "woman-astronaut": {"a": "woman astronaut", "b": "1F469-200D-1F680", "j": ["rocket", "woman", "human", "astronaut", "space"]}, "firefighter": {"a": "firefighter", "b": "1F9D1-200D-1F692", "j": ["firetruck", "fire"]}, "man-firefighter": {"a": "man firefighter", "b": "1F468-200D-1F692", "j": ["firetruck", "man", "human", "fireman", "firefighter"]}, "woman-firefighter": {"a": "woman firefighter", "b": "1F469-200D-1F692", "j": ["firetruck", "woman", "human", "fireman", "firefighter"]}, "police-officer": {"a": "police officer", "b": "1F46E", "j": ["cop", "police", "officer"]}, "man-police-officer": {"a": "man police officer", "b": "1F46E-200D-2642-FE0F", "j": ["police", "man", "officer", "law", "enforcement", "911", "legal", "cop", "arrest"]}, "woman-police-officer": {"a": "woman police officer", "b": "1F46E-200D-2640-FE0F", "j": ["police", "woman", "officer", "law", "enforcement", "arrest", "911", "legal", "cop", "female"]}, "detective": {"a": "detective", "b": "1F575", "j": ["sleuth", "human", "spy"]}, "man-detective": {"a": "man detective", "b": "1F575-FE0F-200D-2642-FE0F", "j": ["spy", "man", "crime", "sleuth", "detective"]}, "woman-detective": {"a": "woman detective", "b": "1F575-FE0F-200D-2640-FE0F", "j": ["spy", "woman", "human", "sleuth", "female", "detective"]}, "guard": {"a": "guard", "b": "1F482", "j": ["protect"]}, "man-guard": {"a": "man guard", "b": "1F482-200D-2642-FE0F", "j": ["guy", "royal", "man", "british", "male", "uk", "gb", "guard"]}, "woman-guard": {"a": "woman guard", "b": "1F482-200D-2640-FE0F", "j": ["royal", "woman", "british", "uk", "gb", "guard", "female"]}, "ninja": {"a": "ninja", "b": "1F977", "j": ["ninjutsu", "japanese", "fighter", "hidden", "stealth", "skills"]}, "construction-worker": {"a": "construction worker", "b": "1F477", "j": ["build", "construction", "worker", "hat", "labor"]}, "man-construction-worker": {"a": "man construction worker", "b": "1F477-200D-2642-FE0F", "j": ["guy", "man", "construction", "male", "worker", "human", "wip", "build", "labor"]}, "woman-construction-worker": {"a": "woman construction worker", "b": "1F477-200D-2640-FE0F", "j": ["build", "construction", "worker", "woman", "human", "wip", "labor", "female"]}, "prince": {"a": "prince", "b": "1F934", "j": ["royal", "man", "male", "crown", "king", "boy"]}, "princess": {"a": "princess", "b": "1F478", "j": ["royal", "woman", "crown", "girl", "fairy tale", "fantasy", "blond", "queen", "female"]}, "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", "male", "hinduism", "turban", "indian", "arabs"]}, "woman-wearing-turban": {"a": "woman wearing turban", "b": "1F473-200D-2640-FE0F", "j": ["woman", "hinduism", "turban", "indian", "arabs", "female"]}, "person-with-skullcap": {"a": "person with skullcap", "b": "1F472", "j": ["person", "man_with_skullcap", "male", "boy", "hat", "skullcap", "cap", "gua pi mao", "chinese"]}, "woman-with-headscarf": {"a": "woman with headscarf", "b": "1F9D5", "j": ["tichel", "mantilla", "bandana", "head kerchief", "headscarf", "female", "hijab"]}, "person-in-tuxedo": {"a": "person in tuxedo", "b": "1F935", "j": ["person", "wedding", "marriage", "tuxedo", "groom", "man_in_tuxedo", "couple"]}, "man-in-tuxedo": {"a": "man in tuxedo", "b": "1F935-200D-2642-FE0F", "j": ["formal", "fashion", "man", "tuxedo"]}, "woman-in-tuxedo": {"a": "woman in tuxedo", "b": "1F935-200D-2640-FE0F", "j": ["formal", "fashion", "tuxedo", "woman"]}, "person-with-veil": {"a": "person with veil", "b": "1F470", "j": ["person", "wedding", "marriage", "woman", "bride", "veil", "bride_with_veil", "couple"]}, "man-with-veil": {"a": "man with veil", "b": "1F470-200D-2642-FE0F", "j": ["veil", "wedding", "man", "marriage"]}, "woman-with-veil": {"a": "woman with veil", "b": "1F470-200D-2640-FE0F", "j": ["veil", "wedding", "marriage", "woman"]}, "pregnant-woman": {"a": "pregnant woman", "b": "1F930", "j": ["pregnant", "baby", "woman"]}, "breastfeeding": {"a": "breast-feeding", "b": "1F931", "j": ["nursing", "breast", "baby", "breast_feeding"]}, "woman-feeding-baby": {"a": "woman feeding baby", "b": "1F469-200D-1F37C", "j": ["nursing", "birth", "woman", "baby", "feeding", "food"]}, "man-feeding-baby": {"a": "man feeding baby", "b": "1F468-200D-1F37C", "j": ["nursing", "birth", "man", "baby", "feeding", "food"]}, "person-feeding-baby": {"a": "person feeding baby", "b": "1F9D1-200D-1F37C", "j": ["nursing", "person", "birth", "baby", "feeding", "food"]}, "baby-angel": {"a": "baby angel", "b": "1F47C", "j": ["heaven", "baby", "face", "fairy tale", "fantasy", "wings", "angel", "halo"]}, "santa-claus": {"a": "Santa Claus", "b": "1F385", "j": ["claus", "man", "male", "father", "santa", "festival", "xmas", "celebration", "Christmas", "father christmas"]}, "mrs-claus": {"a": "Mrs. Claus", "b": "1F936", "j": ["claus", "Mrs.", "woman", "xmas", "celebration", "Christmas", "mother christmas", "mother", "female"]}, "mx-claus": {"a": "mx claus", "b": "1F9D1-200D-1F384", "j": ["Claus, christmas", "christmas"]}, "superhero": {"a": "superhero", "b": "1F9B8", "j": ["marvel", "superpower", "good", "heroine", "hero"]}, "man-superhero": {"a": "man superhero", "b": "1F9B8-200D-2642-FE0F", "j": ["man", "male", "superpowers", "superpower", "good", "hero"]}, "woman-superhero": {"a": "woman superhero", "b": "1F9B8-200D-2640-FE0F", "j": ["woman", "superpowers", "good", "superpower", "heroine", "hero", "female"]}, "supervillain": {"a": "supervillain", "b": "1F9B9", "j": ["marvel", "superpower", "criminal", "villain", "evil"]}, "man-supervillain": {"a": "man supervillain", "b": "1F9B9-200D-2642-FE0F", "j": ["man", "male", "superpowers", "superpower", "criminal", "bad", "villain", "evil", "hero"]}, "woman-supervillain": {"a": "woman supervillain", "b": "1F9B9-200D-2640-FE0F", "j": ["woman", "superpowers", "superpower", "criminal", "bad", "heroine", "villain", "evil", "female"]}, "mage": {"a": "mage", "b": "1F9D9", "j": ["magic", "wizard", "sorcerer", "sorceress", "witch"]}, "man-mage": {"a": "man mage", "b": "1F9D9-200D-2642-FE0F", "j": ["man", "male", "wizard", "sorcerer", "mage"]}, "woman-mage": {"a": "woman mage", "b": "1F9D9-200D-2640-FE0F", "j": ["woman", "mage", "sorceress", "witch", "female"]}, "fairy": {"a": "fairy", "b": "1F9DA", "j": ["magical", "Puck", "Titania", "Oberon", "wings"]}, "man-fairy": {"a": "man fairy", "b": "1F9DA-200D-2642-FE0F", "j": ["Puck", "Oberon", "man", "male"]}, "woman-fairy": {"a": "woman fairy", "b": "1F9DA-200D-2640-FE0F", "j": ["Titania", "woman", "female"]}, "vampire": {"a": "vampire", "b": "1F9DB", "j": ["undead", "blood", "twilight", "Dracula"]}, "man-vampire": {"a": "man vampire", "b": "1F9DB-200D-2642-FE0F", "j": ["dracula", "man", "male", "undead", "Dracula"]}, "woman-vampire": {"a": "woman vampire", "b": "1F9DB-200D-2640-FE0F", "j": ["woman", "undead", "female"]}, "merperson": {"a": "merperson", "b": "1F9DC", "j": ["mermaid", "merwoman", "sea", "merman"]}, "merman": {"a": "merman", "b": "1F9DC-200D-2642-FE0F", "j": ["triton", "Triton", "man", "male"]}, "mermaid": {"a": "mermaid", "b": "1F9DC-200D-2640-FE0F", "j": ["woman", "merwoman", "ariel", "female"]}, "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": ["woman", "magical", "female"]}, "genie": {"a": "genie", "b": "1F9DE", "j": ["(non-human color)", "magical", "wishes", "djinn"]}, "man-genie": {"a": "man genie", "b": "1F9DE-200D-2642-FE0F", "j": ["male", "man", "djinn"]}, "woman-genie": {"a": "woman genie", "b": "1F9DE-200D-2640-FE0F", "j": ["female", "djinn", "woman"]}, "zombie": {"a": "zombie", "b": "1F9DF", "j": ["undead", "dead", "walking dead", "(non-human color)"]}, "man-zombie": {"a": "man zombie", "b": "1F9DF-200D-2642-FE0F", "j": ["dracula", "man", "male", "undead", "walking dead"]}, "woman-zombie": {"a": "woman zombie", "b": "1F9DF-200D-2640-FE0F", "j": ["woman", "undead", "walking dead", "female"]}, "person-getting-massage": {"a": "person getting massage", "b": "1F486", "j": ["relax", "salon", "massage", "face"]}, "man-getting-massage": {"a": "man getting massage", "b": "1F486-200D-2642-FE0F", "j": ["man", "massage", "male", "boy", "face", "head"]}, "woman-getting-massage": {"a": "woman getting massage", "b": "1F486-200D-2640-FE0F", "j": ["massage", "woman", "girl", "face", "head", "female"]}, "person-getting-haircut": {"a": "person getting haircut", "b": "1F487", "j": ["barber", "haircut", "hairstyle", "beauty", "parlor"]}, "man-getting-haircut": {"a": "man getting haircut", "b": "1F487-200D-2642-FE0F", "j": ["boy", "male", "man", "haircut"]}, "woman-getting-haircut": {"a": "woman getting haircut", "b": "1F487-200D-2640-FE0F", "j": ["woman", "girl", "haircut", "female"]}, "person-walking": {"a": "person walking", "b": "1F6B6", "j": ["move", "walking", "walk", "hike"]}, "man-walking": {"a": "man walking", "b": "1F6B6-200D-2642-FE0F", "j": ["man", "human", "steps", "feet", "hike", "walk"]}, "woman-walking": {"a": "woman walking", "b": "1F6B6-200D-2640-FE0F", "j": ["woman", "human", "steps", "feet", "hike", "walk", "female"]}, "person-standing": {"a": "person standing", "b": "1F9CD", "j": ["stand", "standing", "still"]}, "man-standing": {"a": "man standing", "b": "1F9CD-200D-2642-FE0F", "j": ["standing", "man", "still"]}, "woman-standing": {"a": "woman standing", "b": "1F9CD-200D-2640-FE0F", "j": ["standing", "still", "woman"]}, "person-kneeling": {"a": "person kneeling", "b": "1F9CE", "j": ["respectful", "pray", "kneeling", "kneel"]}, "man-kneeling": {"a": "man kneeling", "b": "1F9CE-200D-2642-FE0F", "j": ["respectful", "pray", "man", "kneeling"]}, "woman-kneeling": {"a": "woman kneeling", "b": "1F9CE-200D-2640-FE0F", "j": ["respectful", "pray", "kneeling", "woman"]}, "person-with-white-cane": {"a": "person with white cane", "b": "1F9D1-200D-1F9AF", "j": ["person_with_probing_cane", "blind", "accessibility"]}, "man-with-white-cane": {"a": "man with white cane", "b": "1F468-200D-1F9AF", "j": ["man", "blind", "accessibility", "man_with_probing_cane"]}, "woman-with-white-cane": {"a": "woman with white cane", "b": "1F469-200D-1F9AF", "j": ["woman_with_probing_cane", "blind", "accessibility", "woman"]}, "person-in-motorized-wheelchair": {"a": "person in motorized wheelchair", "b": "1F9D1-200D-1F9BC", "j": ["wheelchair", "disability", "accessibility"]}, "man-in-motorized-wheelchair": {"a": "man in motorized wheelchair", "b": "1F468-200D-1F9BC", "j": ["wheelchair", "disability", "man", "accessibility"]}, "woman-in-motorized-wheelchair": {"a": "woman in motorized wheelchair", "b": "1F469-200D-1F9BC", "j": ["wheelchair", "disability", "accessibility", "woman"]}, "person-in-manual-wheelchair": {"a": "person in manual wheelchair", "b": "1F9D1-200D-1F9BD", "j": ["wheelchair", "disability", "accessibility"]}, "man-in-manual-wheelchair": {"a": "man in manual wheelchair", "b": "1F468-200D-1F9BD", "j": ["wheelchair", "disability", "man", "accessibility"]}, "woman-in-manual-wheelchair": {"a": "woman in manual wheelchair", "b": "1F469-200D-1F9BD", "j": ["wheelchair", "disability", "accessibility", "woman"]}, "person-running": {"a": "person running", "b": "1F3C3", "j": ["running", "marathon", "move"]}, "man-running": {"a": "man running", "b": "1F3C3-200D-2642-FE0F", "j": ["man", "walking", "racing", "race", "running", "exercise", "marathon"]}, "woman-running": {"a": "woman running", "b": "1F3C3-200D-2640-FE0F", "j": ["woman", "walking", "racing", "race", "running", "exercise", "marathon", "female"]}, "woman-dancing": {"a": "woman dancing", "b": "1F483", "j": ["woman", "girl", "dance", "dancing", "fun", "female"]}, "man-dancing": {"a": "man dancing", "b": "1F57A", "j": ["man", "male", "boy", "dance", "dancing", "fun", "dancer"]}, "person-in-suit-levitating": {"a": "person in suit levitating", "b": "1F574", "j": ["person", "business", "jump", "man_in_suit_levitating", "suit", "hover", "levitate"]}, "people-with-bunny-ears": {"a": "people with bunny ears", "b": "1F46F", "j": ["costume", "partying", "perform", "bunny ear", "dancer"]}, "men-with-bunny-ears": {"a": "men with bunny ears", "b": "1F46F-200D-2642-FE0F", "j": ["boys", "male", "partying", "men", "bunny", "bunny ear", "dancer"]}, "women-with-bunny-ears": {"a": "women with bunny ears", "b": "1F46F-200D-2640-FE0F", "j": ["girls", "dancer", "partying", "bunny", "bunny ear", "women", "female"]}, "person-in-steamy-room": {"a": "person in steamy room", "b": "1F9D6", "j": ["spa", "relax", "hamam", "steambath", "sauna", "steam room"]}, "man-in-steamy-room": {"a": "man in steamy room", "b": "1F9D6-200D-2642-FE0F", "j": ["man", "male", "steamroom", "spa", "sauna", "steam room"]}, "woman-in-steamy-room": {"a": "woman in steamy room", "b": "1F9D6-200D-2640-FE0F", "j": ["woman", "steamroom", "spa", "sauna", "steam room", "female"]}, "person-climbing": {"a": "person climbing", "b": "1F9D7", "j": ["sport", "climber"]}, "man-climbing": {"a": "man climbing", "b": "1F9D7-200D-2642-FE0F", "j": ["man", "hobby", "male", "rock", "sports", "climber"]}, "woman-climbing": {"a": "woman climbing", "b": "1F9D7-200D-2640-FE0F", "j": ["hobby", "woman", "rock", "sports", "climber", "female"]}, "person-fencing": {"a": "person fencing", "b": "1F93A", "j": ["sword", "fencer", "fencing", "sports"]}, "horse-racing": {"a": "horse racing", "b": "1F3C7", "j": ["competition", "racehorse", "jockey", "racing", "gambling", "luck", "horse", "betting", "animal"]}, "skier": {"a": "skier", "b": "26F7", "j": ["sports", "snow", "winter", "ski"]}, "snowboarder": {"a": "snowboarder", "b": "1F3C2", "j": ["snow", "winter", "sports", "ski", "snowboard"]}, "person-golfing": {"a": "person golfing", "b": "1F3CC", "j": ["golf", "sports", "business", "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", "woman", "golf", "sports", "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", "sea", "sports", "ocean", "summer", "beach"]}, "woman-surfing": {"a": "woman surfing", "b": "1F3C4-200D-2640-FE0F", "j": ["woman", "surfing", "sea", "sports", "ocean", "summer", "beach", "female"]}, "person-rowing-boat": {"a": "person rowing boat", "b": "1F6A3", "j": ["sport", "rowboat", "move", "boat"]}, "man-rowing-boat": {"a": "man rowing boat", "b": "1F6A3-200D-2642-FE0F", "j": ["man", "hobby", "boat", "water", "sports", "ship", "rowboat"]}, "woman-rowing-boat": {"a": "woman rowing boat", "b": "1F6A3-200D-2640-FE0F", "j": ["hobby", "woman", "boat", "water", "sports", "ship", "rowboat", "female"]}, "person-swimming": {"a": "person swimming", "b": "1F3CA", "j": ["sport", "pool", "swim"]}, "man-swimming": {"a": "man swimming", "b": "1F3CA-200D-2642-FE0F", "j": ["swim", "man", "human", "athlete", "water", "sports", "exercise", "summer"]}, "woman-swimming": {"a": "woman swimming", "b": "1F3CA-200D-2640-FE0F", "j": ["swim", "woman", "human", "athlete", "water", "sports", "exercise", "summer", "female"]}, "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": ["sport", "man", "ball"]}, "woman-bouncing-ball": {"a": "woman bouncing ball", "b": "26F9-FE0F-200D-2640-FE0F", "j": ["woman", "human", "ball", "sports", "female"]}, "person-lifting-weights": {"a": "person lifting weights", "b": "1F3CB", "j": ["sports", "training", "exercise", "weight", "lifter"]}, "man-lifting-weights": {"a": "man lifting weights", "b": "1F3CB-FE0F-200D-2642-FE0F", "j": ["sport", "weight lifter", "man"]}, "woman-lifting-weights": {"a": "woman lifting weights", "b": "1F3CB-FE0F-200D-2640-FE0F", "j": ["woman", "sports", "exercise", "weight lifter", "training", "female"]}, "person-biking": {"a": "person biking", "b": "1F6B4", "j": ["biking", "sport", "bicycle", "move", "cyclist"]}, "man-biking": {"a": "man biking", "b": "1F6B4-200D-2642-FE0F", "j": ["biking", "man", "hipster", "sports", "exercise", "bicycle", "bike", "cyclist"]}, "woman-biking": {"a": "woman biking", "b": "1F6B4-200D-2640-FE0F", "j": ["biking", "woman", "cyclist", "hipster", "sports", "exercise", "bicycle", "bike", "female"]}, "person-mountain-biking": {"a": "person mountain biking", "b": "1F6B5", "j": ["mountain", "bicyclist", "sport", "bicycle", "bike", "move", "cyclist"]}, "man-mountain-biking": {"a": "man mountain biking", "b": "1F6B5-200D-2642-FE0F", "j": ["mountain", "man", "human", "transportation", "sports", "race", "bicycle", "bike", "cyclist"]}, "woman-mountain-biking": {"a": "woman mountain biking", "b": "1F6B5-200D-2640-FE0F", "j": ["biking", "mountain", "woman", "human", "cyclist", "transportation", "sports", "race", "bicycle", "bike", "female"]}, "person-cartwheeling": {"a": "person cartwheeling", "b": "1F938", "j": ["sport", "cartwheel", "gymnastic", "gymnastics"]}, "man-cartwheeling": {"a": "man cartwheeling", "b": "1F938-200D-2642-FE0F", "j": ["cartwheel", "man", "gymnastics"]}, "woman-cartwheeling": {"a": "woman cartwheeling", "b": "1F938-200D-2640-FE0F", "j": ["cartwheel", "gymnastics", "woman"]}, "people-wrestling": {"a": "people wrestling", "b": "1F93C", "j": ["wrestle", "sport", "wrestler"]}, "men-wrestling": {"a": "men wrestling", "b": "1F93C-200D-2642-FE0F", "j": ["wrestle", "wrestlers", "sports", "men"]}, "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", "polo", "water"]}, "man-playing-water-polo": {"a": "man playing water polo", "b": "1F93D-200D-2642-FE0F", "j": ["pool", "water polo", "man", "sports"]}, "woman-playing-water-polo": {"a": "woman playing water polo", "b": "1F93D-200D-2640-FE0F", "j": ["woman", "pool", "water polo", "sports"]}, "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": ["handball", "man", "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": ["performance", "balance", "skill", "multitask", "juggle"]}, "man-juggling": {"a": "man juggling", "b": "1F939-200D-2642-FE0F", "j": ["juggling", "balance", "man", "skill", "multitask", "juggle"]}, "woman-juggling": {"a": "woman juggling", "b": "1F939-200D-2640-FE0F", "j": ["juggling", "balance", "skill", "woman", "multitask", "juggle"]}, "person-in-lotus-position": {"a": "person in lotus position", "b": "1F9D8", "j": ["meditation", "meditate", "serenity", "yoga"]}, "man-in-lotus-position": {"a": "man in lotus position", "b": "1F9D8-200D-2642-FE0F", "j": ["man", "male", "meditation", "mindfulness", "yoga", "zen", "serenity"]}, "woman-in-lotus-position": {"a": "woman in lotus position", "b": "1F9D8-200D-2640-FE0F", "j": ["woman", "meditation", "mindfulness", "yoga", "zen", "serenity", "female"]}, "person-taking-bath": {"a": "person taking bath", "b": "1F6C0", "j": ["bathroom", "bath", "clean", "shower", "bathtub"]}, "person-in-bed": {"a": "person in bed", "b": "1F6CC", "j": ["sleep", "rest", "hotel", "bed"]}, "people-holding-hands": {"a": "people holding hands", "b": "1F9D1-200D-1F91D-200D-1F9D1", "j": ["person", "friendship", "hold", "hand", "holding hands", "couple"]}, "women-holding-hands": {"a": "women holding hands", "b": "1F46D", "j": ["friendship", "people", "love", "like", "human", "hand", "holding hands", "women", "couple", "pair", "female"]}, "woman-and-man-holding-hands": {"a": "woman and man holding hands", "b": "1F46B", "j": ["love", "dating", "like", "woman", "marriage", "human", "hand", "valentines", "affection", "couple", "pair", "date", "people", "man", "hold", "holding hands"]}, "men-holding-hands": {"a": "men holding hands", "b": "1F46C", "j": ["friendship", "people", "love", "man", "twins", "like", "human", "bromance", "holding hands", "zodiac", "Gemini", "men", "couple", "pair"]}, "kiss": {"a": "kiss", "b": "1F48F", "j": ["love", "dating", "like", "marriage", "valentines", "couple", "pair"]}, "kiss-woman-man": {"a": "kiss: woman, man", "b": "1F469-200D-2764-FE0F-200D-1F48B-200D-1F468", "j": ["love", "man", "woman", "kiss", "couple"]}, "kiss-man-man": {"a": "kiss: man, man", "b": "1F468-200D-2764-FE0F-200D-1F48B-200D-1F468", "j": ["love", "man", "like", "dating", "marriage", "valentines", "kiss", "couple", "pair"]}, "kiss-woman-woman": {"a": "kiss: woman, woman", "b": "1F469-200D-2764-FE0F-200D-1F48B-200D-1F469", "j": ["love", "like", "dating", "woman", "marriage", "valentines", "kiss", "couple", "pair"]}, "couple-with-heart": {"a": "couple with heart", "b": "1F491", "j": ["love", "like", "dating", "marriage", "human", "valentines", "affection", "couple", "pair"]}, "couple-with-heart-woman-man": {"a": "couple with heart: woman, man", "b": "1F469-200D-2764-FE0F-200D-1F468", "j": ["love", "man", "woman", "couple with heart", "couple"]}, "couple-with-heart-man-man": {"a": "couple with heart: man, man", "b": "1F468-200D-2764-FE0F-200D-1F468", "j": ["love", "man", "like", "dating", "marriage", "human", "couple with heart", "valentines", "affection", "couple", "pair"]}, "couple-with-heart-woman-woman": {"a": "couple with heart: woman, woman", "b": "1F469-200D-2764-FE0F-200D-1F469", "j": ["love", "like", "dating", "woman", "marriage", "human", "couple with heart", "valentines", "affection", "couple", "pair"]}, "family": {"a": "family", "b": "1F46A", "j": ["people", "mom", "father", "human", "parents", "dad", "home", "child", "mother"]}, "family-man-woman-boy": {"a": "family: man, woman, boy", "b": "1F468-200D-1F469-200D-1F466", "j": ["love", "man", "woman", "boy", "family"]}, "family-man-woman-girl": {"a": "family: man, woman, girl", "b": "1F468-200D-1F469-200D-1F467", "j": ["people", "man", "woman", "human", "girl", "parents", "family", "home", "child"]}, "family-man-woman-girl-boy": {"a": "family: man, woman, girl, boy", "b": "1F468-200D-1F469-200D-1F467-200D-1F466", "j": ["people", "man", "woman", "human", "boy", "girl", "parents", "family", "home", "children"]}, "family-man-woman-boy-boy": {"a": "family: man, woman, boy, boy", "b": "1F468-200D-1F469-200D-1F466-200D-1F466", "j": ["people", "man", "woman", "human", "boy", "parents", "family", "home", "children"]}, "family-man-woman-girl-girl": {"a": "family: man, woman, girl, girl", "b": "1F468-200D-1F469-200D-1F467-200D-1F467", "j": ["people", "man", "woman", "human", "girl", "parents", "family", "home", "children"]}, "family-man-man-boy": {"a": "family: man, man, boy", "b": "1F468-200D-1F468-200D-1F466", "j": ["people", "man", "human", "boy", "parents", "family", "home", "children"]}, "family-man-man-girl": {"a": "family: man, man, girl", "b": "1F468-200D-1F468-200D-1F467", "j": ["people", "man", "human", "girl", "parents", "family", "home", "children"]}, "family-man-man-girl-boy": {"a": "family: man, man, girl, boy", "b": "1F468-200D-1F468-200D-1F467-200D-1F466", "j": ["people", "man", "human", "boy", "girl", "parents", "family", "home", "children"]}, "family-man-man-boy-boy": {"a": "family: man, man, boy, boy", "b": "1F468-200D-1F468-200D-1F466-200D-1F466", "j": ["people", "man", "human", "boy", "parents", "family", "home", "children"]}, "family-man-man-girl-girl": {"a": "family: man, man, girl, girl", "b": "1F468-200D-1F468-200D-1F467-200D-1F467", "j": ["people", "man", "human", "girl", "parents", "family", "home", "children"]}, "family-woman-woman-boy": {"a": "family: woman, woman, boy", "b": "1F469-200D-1F469-200D-1F466", "j": ["people", "woman", "human", "boy", "parents", "family", "home", "children"]}, "family-woman-woman-girl": {"a": "family: woman, woman, girl", "b": "1F469-200D-1F469-200D-1F467", "j": ["people", "woman", "human", "girl", "parents", "family", "home", "children"]}, "family-woman-woman-girl-boy": {"a": "family: woman, woman, girl, boy", "b": "1F469-200D-1F469-200D-1F467-200D-1F466", "j": ["people", "woman", "human", "boy", "girl", "parents", "family", "home", "children"]}, "family-woman-woman-boy-boy": {"a": "family: woman, woman, boy, boy", "b": "1F469-200D-1F469-200D-1F466-200D-1F466", "j": ["people", "woman", "human", "boy", "parents", "family", "home", "children"]}, "family-woman-woman-girl-girl": {"a": "family: woman, woman, girl, girl", "b": "1F469-200D-1F469-200D-1F467-200D-1F467", "j": ["people", "woman", "human", "girl", "parents", "family", "home", "children"]}, "family-man-boy": {"a": "family: man, boy", "b": "1F468-200D-1F466", "j": ["people", "man", "human", "boy", "parent", "family", "home", "child"]}, "family-man-boy-boy": {"a": "family: man, boy, boy", "b": "1F468-200D-1F466-200D-1F466", "j": ["people", "man", "human", "boy", "parent", "family", "home", "children"]}, "family-man-girl": {"a": "family: man, girl", "b": "1F468-200D-1F467", "j": ["people", "man", "human", "girl", "parent", "family", "home", "child"]}, "family-man-girl-boy": {"a": "family: man, girl, boy", "b": "1F468-200D-1F467-200D-1F466", "j": ["people", "man", "human", "boy", "girl", "parent", "family", "home", "children"]}, "family-man-girl-girl": {"a": "family: man, girl, girl", "b": "1F468-200D-1F467-200D-1F467", "j": ["people", "man", "human", "girl", "parent", "family", "home", "children"]}, "family-woman-boy": {"a": "family: woman, boy", "b": "1F469-200D-1F466", "j": ["people", "woman", "human", "boy", "parent", "family", "home", "child"]}, "family-woman-boy-boy": {"a": "family: woman, boy, boy", "b": "1F469-200D-1F466-200D-1F466", "j": ["people", "woman", "human", "boy", "parent", "family", "home", "children"]}, "family-woman-girl": {"a": "family: woman, girl", "b": "1F469-200D-1F467", "j": ["people", "woman", "human", "girl", "parent", "family", "home", "child"]}, "family-woman-girl-boy": {"a": "family: woman, girl, boy", "b": "1F469-200D-1F467-200D-1F466", "j": ["people", "woman", "human", "boy", "girl", "parent", "family", "home", "children"]}, "family-woman-girl-girl": {"a": "family: woman, girl, girl", "b": "1F469-200D-1F467-200D-1F467", "j": ["people", "woman", "human", "girl", "parent", "family", "home", "children"]}, "speaking-head": {"a": "speaking head", "b": "1F5E3", "j": ["person", "talk", "human", "sing", "face", "silhouette", "speak", "say", "speaking", "user", "head"]}, "bust-in-silhouette": {"a": "bust in silhouette", "b": "1F464", "j": ["person", "bust", "human", "silhouette", "user"]}, "busts-in-silhouette": {"a": "busts in silhouette", "b": "1F465", "j": ["team", "bust", "person", "group", "human", "silhouette", "user"]}, "people-hugging": {"a": "people hugging", "b": "1FAC2", "j": ["hug", "hello", "thanks", "goodbye", "care"]}, "footprints": {"a": "footprints", "b": "1F463", "j": ["tracking", "footprint", "walking", "clothing", "feet", "print", "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": ["monkey", "face", "nature", "circus", "animal"]}, "monkey": {"a": "monkey", "b": "1F412", "j": ["banana", "circus", "animal", "nature"]}, "gorilla": {"a": "gorilla", "b": "1F98D", "j": ["circus", "animal", "nature"]}, "orangutan": {"a": "orangutan", "b": "1F9A7", "j": ["animal", "ape"]}, "dog-face": {"a": "dog face", "b": "1F436", "j": ["puppy", "pet", "dog", "face", "nature", "woof", "faithful", "friend", "animal"]}, "dog": {"a": "dog", "b": "1F415", "j": ["pet", "nature", "faithful", "friend", "doge", "animal"]}, "guide-dog": {"a": "guide dog", "b": "1F9AE", "j": ["guide", "animal", "blind", "accessibility"]}, "service-dog": {"a": "service dog", "b": "1F415-200D-1F9BA", "j": ["animal", "accessibility", "dog", "assistance", "service", "blind"]}, "poodle": {"a": "poodle", "b": "1F429", "j": ["pet", "dog", "nature", "101", "animal"]}, "wolf": {"a": "wolf", "b": "1F43A", "j": ["animal", "wild", "face", "nature"]}, "fox": {"a": "fox", "b": "1F98A", "j": ["animal", "face", "nature"]}, "raccoon": {"a": "raccoon", "b": "1F99D", "j": ["curious", "sly", "animal", "nature"]}, "cat-face": {"a": "cat face", "b": "1F431", "j": ["pet", "meow", "kitten", "face", "nature", "cat", "animal"]}, "cat": {"a": "cat", "b": "1F408", "j": ["meow", "pet", "animal", "cats"]}, "black-cat": {"a": "black cat", "b": "1F408-200D-2B1B", "j": ["unlucky", "superstition", "luck", "cat", "black"]}, "lion": {"a": "lion", "b": "1F981", "j": ["zodiac", "Leo", "face", "nature", "animal"]}, "tiger-face": {"a": "tiger face", "b": "1F42F", "j": ["danger", "tiger", "face", "nature", "wild", "cat", "animal", "roar"]}, "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", "face", "nature", "horse", "animal"]}, "horse": {"a": "horse", "b": "1F40E", "j": ["equestrian", "gamble", "racing", "luck", "racehorse", "animal"]}, "unicorn": {"a": "unicorn", "b": "1F984", "j": ["mystical", "animal", "face", "nature"]}, "zebra": {"a": "zebra", "b": "1F993", "j": ["nature", "safari", "stripe", "stripes", "animal"]}, "deer": {"a": "deer", "b": "1F98C", "j": ["venison", "animal", "horns", "nature"]}, "bison": {"a": "bison", "b": "1F9AC", "j": ["wisent", "buffalo", "herd", "ox"]}, "cow-face": {"a": "cow face", "b": "1F42E", "j": ["cow", "milk", "beef", "ox", "face", "nature", "moo", "animal"]}, "ox": {"a": "ox", "b": "1F402", "j": ["cow", "Taurus", "beef", "zodiac", "bull", "animal"]}, "water-buffalo": {"a": "water buffalo", "b": "1F403", "j": ["cow", "buffalo", "water", "ox", "nature", "animal"]}, "cow": {"a": "cow", "b": "1F404", "j": ["milk", "beef", "ox", "nature", "moo", "animal"]}, "pig-face": {"a": "pig face", "b": "1F437", "j": ["pig", "face", "nature", "oink", "animal"]}, "pig": {"a": "pig", "b": "1F416", "j": ["nature", "animal", "sow"]}, "boar": {"a": "boar", "b": "1F417", "j": ["animal", "pig", "nature"]}, "pig-nose": {"a": "pig nose", "b": "1F43D", "j": ["pig", "face", "oink", "nose", "animal"]}, "ram": {"a": "ram", "b": "1F40F", "j": ["male", "zodiac", "nature", "sheep", "Aries", "animal"]}, "ewe": {"a": "ewe", "b": "1F411", "j": ["nature", "sheep", "shipit", "wool", "animal", "female"]}, "goat": {"a": "goat", "b": "1F410", "j": ["animal", "Capricorn", "zodiac", "nature"]}, "camel": {"a": "camel", "b": "1F42A", "j": ["hump", "dromedary", "hot", "desert", "animal"]}, "twohump-camel": {"a": "two-hump camel", "b": "1F42B", "j": ["animal", "hump", "bactrian", "hot", "desert", "nature", "camel", "two_hump_camel"]}, "llama": {"a": "llama", "b": "1F999", "j": ["guanaco", "vicuña", "alpaca", "nature", "wool", "animal"]}, "giraffe": {"a": "giraffe", "b": "1F992", "j": ["spots", "animal", "safari", "nature"]}, "elephant": {"a": "elephant", "b": "1F418", "j": ["nature", "nose", "circus", "animal", "th"]}, "mammoth": {"a": "mammoth", "b": "1F9A3", "j": ["large", "woolly", "elephant", "extinction", "tusks", "tusk"]}, "rhinoceros": {"a": "rhinoceros", "b": "1F98F", "j": ["nature", "animal", "horn"]}, "hippopotamus": {"a": "hippopotamus", "b": "1F99B", "j": ["nature", "animal", "hippo"]}, "mouse-face": {"a": "mouse face", "b": "1F42D", "j": ["mouse", "face", "nature", "animal", "cheese_wedge", "rodent"]}, "mouse": {"a": "mouse", "b": "1F401", "j": ["rodent", "animal", "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": ["pet", "spring", "face", "nature", "rabbit", "bunny", "animal", "magic"]}, "rabbit": {"a": "rabbit", "b": "1F407", "j": ["pet", "spring", "nature", "bunny", "animal", "magic"]}, "chipmunk": {"a": "chipmunk", "b": "1F43F", "j": ["nature", "animal", "squirrel", "rodent"]}, "beaver": {"a": "beaver", "b": "1F9AB", "j": ["animal", "dam", "rodent"]}, "hedgehog": {"a": "hedgehog", "b": "1F994", "j": ["spiny", "animal", "nature"]}, "bat": {"a": "bat", "b": "1F987", "j": ["blind", "animal", "vampire", "nature"]}, "bear": {"a": "bear", "b": "1F43B", "j": ["animal", "wild", "face", "nature"]}, "polar-bear": {"a": "polar bear", "b": "1F43B-200D-2744-FE0F", "j": ["animal", "white", "arctic", "bear"]}, "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", "animal", "lazy"]}, "otter": {"a": "otter", "b": "1F9A6", "j": ["playful", "animal", "fishing"]}, "skunk": {"a": "skunk", "b": "1F9A8", "j": ["stink", "animal"]}, "kangaroo": {"a": "kangaroo", "b": "1F998", "j": ["Australia", "australia", "hop", "joey", "nature", "marsupial", "animal", "jump"]}, "badger": {"a": "badger", "b": "1F9A1", "j": ["nature", "pester", "honey badger", "honey", "animal"]}, "paw-prints": {"a": "paw prints", "b": "1F43E", "j": ["pet", "tracking", "dog", "feet", "paw", "cat", "print", "animal", "footprints"]}, "turkey": {"a": "turkey", "b": "1F983", "j": ["animal", "bird"]}, "chicken": {"a": "chicken", "b": "1F414", "j": ["cluck", "animal", "bird", "nature"]}, "rooster": {"a": "rooster", "b": "1F413", "j": ["chicken", "animal", "bird", "nature"]}, "hatching-chick": {"a": "hatching chick", "b": "1F423", "j": ["born", "hatching", "baby", "bird", "chicken", "chick", "egg", "animal"]}, "baby-chick": {"a": "baby chick", "b": "1F424", "j": ["baby", "bird", "chicken", "chick", "animal"]}, "frontfacing-baby-chick": {"a": "front-facing baby chick", "b": "1F425", "j": ["baby", "bird", "chicken", "front_facing_baby_chick", "chick", "animal"]}, "bird": {"a": "bird", "b": "1F426", "j": ["tweet", "spring", "nature", "fly", "animal"]}, "penguin": {"a": "penguin", "b": "1F427", "j": ["animal", "bird", "nature"]}, "dove": {"a": "dove", "b": "1F54A", "j": ["fly", "animal", "bird", "peace"]}, "eagle": {"a": "eagle", "b": "1F985", "j": ["animal", "bird", "nature"]}, "duck": {"a": "duck", "b": "1F986", "j": ["animal", "bird", "mallard", "nature"]}, "swan": {"a": "swan", "b": "1F9A2", "j": ["ugly duckling", "cygnet", "bird", "nature", "animal"]}, "owl": {"a": "owl", "b": "1F989", "j": ["bird", "wise", "nature", "hoot", "animal"]}, "dodo": {"a": "dodo", "b": "1F9A4", "j": ["large", "bird", "Mauritius", "extinction", "animal"]}, "feather": {"a": "feather", "b": "1FAB6", "j": ["flight", "bird", "plumage", "light", "fly"]}, "flamingo": {"a": "flamingo", "b": "1F9A9", "j": ["tropical", "flamboyant", "animal"]}, "peacock": {"a": "peacock", "b": "1F99A", "j": ["peahen", "bird", "proud", "nature", "ostentatious", "animal"]}, "parrot": {"a": "parrot", "b": "1F99C", "j": ["talk", "pirate", "bird", "nature", "animal"]}, "frog": {"a": "frog", "b": "1F438", "j": ["croak", "face", "toad", "nature", "animal"]}, "crocodile": {"a": "crocodile", "b": "1F40A", "j": ["reptile", "alligator", "nature", "animal", "lizard"]}, "turtle": {"a": "turtle", "b": "1F422", "j": ["slow", "nature", "tortoise", "terrapin", "animal"]}, "lizard": {"a": "lizard", "b": "1F98E", "j": ["reptile", "animal", "nature"]}, "snake": {"a": "snake", "b": "1F40D", "j": ["serpent", "Ophiuchus", "zodiac", "nature", "hiss", "bearer", "evil", "animal", "python"]}, "dragon-face": {"a": "dragon face", "b": "1F432", "j": ["face", "myth", "fairy tale", "nature", "dragon", "chinese", "green", "animal"]}, "dragon": {"a": "dragon", "b": "1F409", "j": ["myth", "nature", "fairy tale", "chinese", "green", "animal"]}, "sauropod": {"a": "sauropod", "b": "1F995", "j": ["diplodocus", "brontosaurus", "nature", "dinosaur", "extinct", "animal", "brachiosaurus"]}, "trex": {"a": "T-Rex", "b": "1F996", "j": ["nature", "Tyrannosaurus Rex", "dinosaur", "t_rex", "tyrannosaurus", "extinct", "animal"]}, "spouting-whale": {"a": "spouting whale", "b": "1F433", "j": ["spouting", "whale", "sea", "face", "nature", "ocean", "animal"]}, "whale": {"a": "whale", "b": "1F40B", "j": ["ocean", "animal", "sea", "nature"]}, "dolphin": {"a": "dolphin", "b": "1F42C", "j": ["sea", "fish", "nature", "flipper", "ocean", "animal", "fins", "beach"]}, "seal": {"a": "seal", "b": "1F9AD", "j": ["sea", "sea lion", "animal", "creature"]}, "fish": {"a": "fish", "b": "1F41F", "j": ["zodiac", "nature", "Pisces", "food", "animal"]}, "tropical-fish": {"a": "tropical fish", "b": "1F420", "j": ["swim", "fish", "ocean", "nemo", "tropical", "animal", "beach"]}, "blowfish": {"a": "blowfish", "b": "1F421", "j": ["sea", "fish", "nature", "ocean", "food", "animal"]}, "shark": {"a": "shark", "b": "1F988", "j": ["jaws", "sea", "fish", "nature", "ocean", "animal", "fins", "beach"]}, "octopus": {"a": "octopus", "b": "1F419", "j": ["sea", "nature", "ocean", "creature", "animal", "beach"]}, "spiral-shell": {"a": "spiral shell", "b": "1F41A", "j": ["sea", "shell", "nature", "spiral", "beach"]}, "snail": {"a": "snail", "b": "1F40C", "j": ["slow", "animal", "shell"]}, "butterfly": {"a": "butterfly", "b": "1F98B", "j": ["caterpillar", "nature", "pretty", "insect", "animal"]}, "bug": {"a": "bug", "b": "1F41B", "j": ["worm", "insect", "animal", "nature"]}, "ant": {"a": "ant", "b": "1F41C", "j": ["bug", "insect", "animal", "nature"]}, "honeybee": {"a": "honeybee", "b": "1F41D", "j": ["spring", "nature", "bug", "bee", "honey", "insect", "animal"]}, "beetle": {"a": "beetle", "b": "1FAB2", "j": ["insect", "bug"]}, "lady-beetle": {"a": "lady beetle", "b": "1F41E", "j": ["beetle", "nature", "ladybird", "ladybug", "insect", "animal"]}, "cricket": {"a": "cricket", "b": "1F997", "j": ["chirp", "animal", "Orthoptera", "grasshopper"]}, "cockroach": {"a": "cockroach", "b": "1FAB3", "j": ["pest", "pests", "insect", "roach"]}, "spider": {"a": "spider", "b": "1F577", "j": ["arachnid", "animal", "insect"]}, "spider-web": {"a": "spider web", "b": "1F578", "j": ["silk", "spider", "arachnid", "insect", "animal", "web"]}, "scorpion": {"a": "scorpion", "b": "1F982", "j": ["Scorpio", "zodiac", "scorpio", "arachnid", "animal"]}, "mosquito": {"a": "mosquito", "b": "1F99F", "j": ["malaria", "pest", "nature", "disease", "fever", "insect", "animal", "virus"]}, "fly": {"a": "fly", "b": "1FAB0", "j": ["pest", "rotting", "disease", "maggot", "insect"]}, "worm": {"a": "worm", "b": "1FAB1", "j": ["earthworm", "animal", "annelid", "parasite"]}, "microbe": {"a": "microbe", "b": "1F9A0", "j": ["bacteria", "germs", "amoeba", "virus"]}, "bouquet": {"a": "bouquet", "b": "1F490", "j": ["flowers", "flower", "spring", "nature"]}, "cherry-blossom": {"a": "cherry blossom", "b": "1F338", "j": ["flower", "spring", "nature", "plant", "cherry", "blossom"]}, "white-flower": {"a": "white flower", "b": "1F4AE", "j": ["flower", "japanese", "spring"]}, "rosette": {"a": "rosette", "b": "1F3F5", "j": ["military", "plant", "decoration", "flower"]}, "rose": {"a": "rose", "b": "1F339", "j": ["flower", "love", "spring", "valentines", "flowers"]}, "wilted-flower": {"a": "wilted flower", "b": "1F940", "j": ["flower", "plant", "wilted", "nature"]}, "hibiscus": {"a": "hibiscus", "b": "1F33A", "j": ["flower", "plant", "flowers", "vegetable", "beach"]}, "sunflower": {"a": "sunflower", "b": "1F33B", "j": ["sun", "flower", "nature", "fall", "plant"]}, "blossom": {"a": "blossom", "b": "1F33C", "j": ["flowers", "flower", "yellow", "nature"]}, "tulip": {"a": "tulip", "b": "1F337", "j": ["flower", "spring", "nature", "plant", "summer", "flowers"]}, "seedling": {"a": "seedling", "b": "1F331", "j": ["spring", "young", "lawn", "nature", "plant", "grass"]}, "potted-plant": {"a": "potted plant", "b": "1FAB4", "j": ["house", "nurturing", "greenery", "grow", "plant", "useless", "boring"]}, "evergreen-tree": {"a": "evergreen tree", "b": "1F332", "j": ["tree", "plant", "nature"]}, "deciduous-tree": {"a": "deciduous tree", "b": "1F333", "j": ["shedding", "deciduous", "nature", "plant", "tree"]}, "palm-tree": {"a": "palm tree", "b": "1F334", "j": ["nature", "plant", "tree", "mojito", "summer", "tropical", "vegetable", "palm", "beach"]}, "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": ["weed", "leaf", "lawn", "medicine", "plant", "vegetable", "grass"]}, "shamrock": {"a": "shamrock", "b": "2618", "j": ["clover", "nature", "plant", "irish", "vegetable"]}, "four-leaf-clover": {"a": "four leaf clover", "b": "1F340", "j": ["four-leaf clover", "leaf", "lucky", "four", "clover", "nature", "plant", "4", "irish", "vegetable"]}, "maple-leaf": {"a": "maple leaf", "b": "1F341", "j": ["leaf", "maple", "ca", "nature", "fall", "plant", "falling", "vegetable"]}, "fallen-leaf": {"a": "fallen leaf", "b": "1F342", "j": ["leaf", "nature", "plant", "falling", "leaves", "vegetable"]}, "leaf-fluttering-in-wind": {"a": "leaf fluttering in wind", "b": "1F343", "j": ["flutter", "leaf", "spring", "lawn", "blow", "nature", "plant", "tree", "vegetable", "wind", "grass"]}, "grapes": {"a": "grapes", "b": "1F347", "j": ["wine", "grape", "food", "fruit"]}, "melon": {"a": "melon", "b": "1F348", "j": ["nature", "food", "fruit"]}, "watermelon": {"a": "watermelon", "b": "1F349", "j": ["fruit", "food", "picnic", "summer"]}, "tangerine": {"a": "tangerine", "b": "1F34A", "j": ["fruit", "food", "orange", "nature"]}, "lemon": {"a": "lemon", "b": "1F34B", "j": ["citrus", "nature", "fruit"]}, "banana": {"a": "banana", "b": "1F34C", "j": ["monkey", "food", "fruit"]}, "pineapple": {"a": "pineapple", "b": "1F34D", "j": ["nature", "food", "fruit"]}, "mango": {"a": "mango", "b": "1F96D", "j": ["tropical", "food", "fruit"]}, "red-apple": {"a": "red apple", "b": "1F34E", "j": ["apple", "fruit", "school", "mac", "red"]}, "green-apple": {"a": "green apple", "b": "1F34F", "j": ["fruit", "apple", "green", "nature"]}, "pear": {"a": "pear", "b": "1F350", "j": ["nature", "food", "fruit"]}, "peach": {"a": "peach", "b": "1F351", "j": ["nature", "food", "fruit"]}, "cherries": {"a": "cherries", "b": "1F352", "j": ["fruit", "red", "cherry", "food", "berries"]}, "strawberry": {"a": "strawberry", "b": "1F353", "j": ["fruit", "food", "berry", "nature"]}, "blueberries": {"a": "blueberries", "b": "1FAD0", "j": ["blue", "blueberry", "berry", "bilberry", "fruit"]}, "kiwi-fruit": {"a": "kiwi fruit", "b": "1F95D", "j": ["kiwi", "food", "fruit"]}, "tomato": {"a": "tomato", "b": "1F345", "j": ["fruit", "food", "vegetable", "nature"]}, "olive": {"a": "olive", "b": "1FAD2", "j": ["food", "fruit"]}, "coconut": {"a": "coconut", "b": "1F965", "j": ["fruit", "piña colada", "nature", "food", "palm"]}, "avocado": {"a": "avocado", "b": "1F951", "j": ["food", "fruit"]}, "eggplant": {"a": "eggplant", "b": "1F346", "j": ["aubergine", "food", "vegetable", "nature"]}, "potato": {"a": "potato", "b": "1F954", "j": ["tuber", "starch", "vegatable", "food", "vegetable"]}, "carrot": {"a": "carrot", "b": "1F955", "j": ["food", "vegetable", "orange"]}, "ear-of-corn": {"a": "ear of corn", "b": "1F33D", "j": ["corn", "maze", "ear", "food", "plant", "maize", "vegetable"]}, "hot-pepper": {"a": "hot pepper", "b": "1F336", "j": ["chili", "hot", "spicy", "pepper", "food", "chilli"]}, "bell-pepper": {"a": "bell pepper", "b": "1FAD1", "j": ["fruit", "pepper", "capsicum", "plant", "vegetable"]}, "cucumber": {"a": "cucumber", "b": "1F952", "j": ["pickle", "food", "vegetable", "fruit"]}, "leafy-green": {"a": "leafy green", "b": "1F96C", "j": ["cabbage", "kale", "bok choy", "plant", "food", "lettuce", "vegetable"]}, "broccoli": {"a": "broccoli", "b": "1F966", "j": ["wild cabbage", "food", "vegetable", "fruit"]}, "garlic": {"a": "garlic", "b": "1F9C4", "j": ["cook", "food", "flavoring", "spice"]}, "onion": {"a": "onion", "b": "1F9C5", "j": ["cook", "food", "flavoring", "spice"]}, "mushroom": {"a": "mushroom", "b": "1F344", "j": ["plant", "vegetable", "toadstool"]}, "peanuts": {"a": "peanuts", "b": "1F95C", "j": ["nut", "food", "vegetable", "peanut"]}, "chestnut": {"a": "chestnut", "b": "1F330", "j": ["plant", "food", "squirrel"]}, "bread": {"a": "bread", "b": "1F35E", "j": ["loaf", "toast", "breakfast", "food", "wheat"]}, "croissant": {"a": "croissant", "b": "1F950", "j": ["french", "bread", "breakfast", "food", "roll"]}, "baguette-bread": {"a": "baguette bread", "b": "1F956", "j": ["french", "baguette", "food", "bread"]}, "flatbread": {"a": "flatbread", "b": "1FAD3", "j": ["arepa", "lavash", "naan", "pita", "food", "flour"]}, "pretzel": {"a": "pretzel", "b": "1F968", "j": ["convoluted", "food", "bread", "twisted"]}, "bagel": {"a": "bagel", "b": "1F96F", "j": ["bread", "bakery", "breakfast", "food", "schmear"]}, "pancakes": {"a": "pancakes", "b": "1F95E", "j": ["hotcakes", "hotcake", "pancake", "flapjacks", "breakfast", "food", "crêpe"]}, "waffle": {"a": "waffle", "b": "1F9C7", "j": ["iron", "food", "breakfast", "indecisive"]}, "cheese-wedge": {"a": "cheese wedge", "b": "1F9C0", "j": ["chadder", "food", "cheese"]}, "meat-on-bone": {"a": "meat on bone", "b": "1F356", "j": ["drumstick", "bone", "good", "meat", "food"]}, "poultry-leg": {"a": "poultry leg", "b": "1F357", "j": ["leg", "drumstick", "bone", "poultry", "bird", "meat", "chicken", "food", "turkey"]}, "cut-of-meat": {"a": "cut of meat", "b": "1F969", "j": ["cow", "steak", "meat", "food", "lambchop", "chop", "porkchop", "cut"]}, "bacon": {"a": "bacon", "b": "1F953", "j": ["pig", "meat", "breakfast", "food", "pork"]}, "hamburger": {"a": "hamburger", "b": "1F354", "j": ["burger king", "fast food", "beef", "meat", "burger", "mcdonalds", "cheeseburger"]}, "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": ["food", "hotdog", "sausage", "frankfurter"]}, "sandwich": {"a": "sandwich", "b": "1F96A", "j": ["lunch", "food", "bread"]}, "taco": {"a": "taco", "b": "1F32E", "j": ["food", "mexican"]}, "burrito": {"a": "burrito", "b": "1F32F", "j": ["food", "mexican", "wrap"]}, "tamale": {"a": "tamale", "b": "1FAD4", "j": ["masa", "food", "wrapped", "mexican"]}, "stuffed-flatbread": {"a": "stuffed flatbread", "b": "1F959", "j": ["kebab", "flatbread", "food", "stuffed", "gyro", "falafel"]}, "falafel": {"a": "falafel", "b": "1F9C6", "j": ["chickpea", "food", "meatball"]}, "egg": {"a": "egg", "b": "1F95A", "j": ["breakfast", "food", "chicken"]}, "cooking": {"a": "cooking", "b": "1F373", "j": ["breakfast", "food", "frying", "kitchen", "egg", "pan"]}, "shallow-pan-of-food": {"a": "shallow pan of food", "b": "1F958", "j": ["cooking", "shallow", "paella", "food", "pan", "casserole"]}, "pot-of-food": {"a": "pot of food", "b": "1F372", "j": ["pot", "stew", "soup", "meat", "food"]}, "fondue": {"a": "fondue", "b": "1FAD5", "j": ["pot", "melted", "cheese", "chocolate", "food", "Swiss"]}, "bowl-with-spoon": {"a": "bowl with spoon", "b": "1F963", "j": ["porridge", "breakfast", "food", "cereal", "congee", "oatmeal"]}, "green-salad": {"a": "green salad", "b": "1F957", "j": ["salad", "healthy", "food", "green", "lettuce"]}, "popcorn": {"a": "popcorn", "b": "1F37F", "j": ["snack", "food", "films", "movie theater"]}, "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": ["food", "box", "japanese", "bento"]}, "rice-cracker": {"a": "rice cracker", "b": "1F358", "j": ["rice", "food", "japanese", "cracker"]}, "rice-ball": {"a": "rice ball", "b": "1F359", "j": ["rice", "Japanese", "japanese", "ball", "food"]}, "cooked-rice": {"a": "cooked rice", "b": "1F35A", "j": ["china", "rice", "asian", "food", "cooked"]}, "curry-rice": {"a": "curry rice", "b": "1F35B", "j": ["curry", "rice", "hot", "spicy", "food", "indian"]}, "steaming-bowl": {"a": "steaming bowl", "b": "1F35C", "j": ["bowl", "japanese", "steaming", "food", "noodle", "ramen", "chopsticks"]}, "spaghetti": {"a": "spaghetti", "b": "1F35D", "j": ["italian", "food", "noodle", "pasta"]}, "roasted-sweet-potato": {"a": "roasted sweet potato", "b": "1F360", "j": ["roasted", "potato", "nature", "sweet", "food"]}, "oden": {"a": "oden", "b": "1F362", "j": ["stick", "kebab", "skewer", "japanese", "food", "seafood"]}, "sushi": {"a": "sushi", "b": "1F363", "j": ["rice", "food", "japanese", "fish"]}, "fried-shrimp": {"a": "fried shrimp", "b": "1F364", "j": ["prawn", "shrimp", "appetizer", "fried", "tempura", "food", "summer", "animal"]}, "fish-cake-with-swirl": {"a": "fish cake with swirl", "b": "1F365", "j": ["beach", "pink", "narutomaki", "sea", "fish", "surimi", "pastry", "cake", "food", "ramen", "swirl", "kamaboko", "japan"]}, "moon-cake": {"a": "moon cake", "b": "1F96E", "j": ["food", "yuèbǐng", "festival", "autumn"]}, "dango": {"a": "dango", "b": "1F361", "j": ["stick", "dessert", "Japanese", "japanese", "meat", "sweet", "barbecue", "food", "skewer"]}, "dumpling": {"a": "dumpling", "b": "1F95F", "j": ["jiaozi", "food", "pierogi", "empanada", "gyōza", "potsticker"]}, "fortune-cookie": {"a": "fortune cookie", "b": "1F960", "j": ["food", "prophecy"]}, "takeout-box": {"a": "takeout box", "b": "1F961", "j": ["oyster pail", "leftovers", "food"]}, "crab": {"a": "crab", "b": "1F980", "j": ["crustacean", "animal", "zodiac", "Cancer"]}, "lobster": {"a": "lobster", "b": "1F99E", "j": ["claws", "bisque", "nature", "animal", "seafood"]}, "shrimp": {"a": "shrimp", "b": "1F990", "j": ["shellfish", "nature", "ocean", "food", "small", "animal", "seafood"]}, "squid": {"a": "squid", "b": "1F991", "j": ["sea", "nature", "ocean", "food", "animal", "molusc"]}, "oyster": {"a": "oyster", "b": "1F9AA", "j": ["food", "pearl", "diving"]}, "soft-ice-cream": {"a": "soft ice cream", "b": "1F366", "j": ["hot", "dessert", "ice", "soft", "icecream", "sweet", "food", "summer", "cream"]}, "shaved-ice": {"a": "shaved ice", "b": "1F367", "j": ["hot", "dessert", "ice", "sweet", "summer", "shaved"]}, "ice-cream": {"a": "ice cream", "b": "1F368", "j": ["hot", "dessert", "ice", "sweet", "food", "cream"]}, "doughnut": {"a": "doughnut", "b": "1F369", "j": ["dessert", "sweet", "breakfast", "food", "snack", "donut"]}, "cookie": {"a": "cookie", "b": "1F36A", "j": ["dessert", "sweet", "chocolate", "food", "snack", "oreo"]}, "birthday-cake": {"a": "birthday cake", "b": "1F382", "j": ["birthday", "dessert", "pastry", "celebration", "sweet", "cake", "food"]}, "shortcake": {"a": "shortcake", "b": "1F370", "j": ["dessert", "pastry", "sweet", "cake", "food", "slice"]}, "cupcake": {"a": "cupcake", "b": "1F9C1", "j": ["sweet", "bakery", "dessert", "food"]}, "pie": {"a": "pie", "b": "1F967", "j": ["fruit", "dessert", "meat", "filling", "pastry", "food"]}, "chocolate-bar": {"a": "chocolate bar", "b": "1F36B", "j": ["dessert", "bar", "sweet", "chocolate", "food", "snack"]}, "candy": {"a": "candy", "b": "1F36C", "j": ["sweet", "snack", "dessert", "lolly"]}, "lollipop": {"a": "lollipop", "b": "1F36D", "j": ["dessert", "sweet", "candy", "food", "snack"]}, "custard": {"a": "custard", "b": "1F36E", "j": ["sweet", "dessert", "food", "pudding"]}, "honey-pot": {"a": "honey pot", "b": "1F36F", "j": ["pot", "kitchen", "bees", "sweet", "honeypot", "honey"]}, "baby-bottle": {"a": "baby bottle", "b": "1F37C", "j": ["milk", "drink", "bottle", "baby", "food", "container"]}, "glass-of-milk": {"a": "glass of milk", "b": "1F95B", "j": ["cow", "milk", "drink", "beverage", "glass"]}, "hot-beverage": {"a": "hot beverage", "b": "2615", "j": ["hot", "beverage", "drink", "steaming", "coffee", "caffeine", "tea", "latte", "espresso"]}, "teapot": {"a": "teapot", "b": "1FAD6", "j": ["hot", "pot", "drink", "tea"]}, "teacup-without-handle": {"a": "teacup without handle", "b": "1F375", "j": ["bowl", "cup", "british", "beverage", "teacup", "drink", "breakfast", "tea", "green"]}, "sake": {"a": "sake", "b": "1F376", "j": ["cup", "wine", "beverage", "drink", "drunk", "bottle", "bar", "japanese", "alcohol", "booze"]}, "bottle-with-popping-cork": {"a": "bottle with popping cork", "b": "1F37E", "j": ["wine", "drink", "bottle", "bar", "celebration", "popping", "cork"]}, "wine-glass": {"a": "wine glass", "b": "1F377", "j": ["wine", "beverage", "drink", "drunk", "alcohol", "bar", "booze", "glass"]}, "cocktail-glass": {"a": "cocktail glass", "b": "1F378", "j": ["drink", "alcohol", "drunk", "beverage", "bar", "cocktail", "mojito", "booze", "glass"]}, "tropical-drink": {"a": "tropical drink", "b": "1F379", "j": ["drink", "beverage", "alcohol", "bar", "cocktail", "mojito", "summer", "tropical", "booze", "beach"]}, "beer-mug": {"a": "beer mug", "b": "1F37A", "j": ["pub", "drink", "beverage", "drunk", "alcohol", "bar", "relax", "mug", "party", "summer", "booze", "beer"]}, "clinking-beer-mugs": {"a": "clinking beer mugs", "b": "1F37B", "j": ["pub", "drink", "beverage", "drunk", "alcohol", "clink", "bar", "relax", "mug", "party", "summer", "booze", "beer"]}, "clinking-glasses": {"a": "clinking glasses", "b": "1F942", "j": ["cheers", "toast", "champagne", "wine", "drink", "beverage", "alcohol", "clink", "party", "glass", "celebrate"]}, "tumbler-glass": {"a": "tumbler glass", "b": "1F943", "j": ["bourbon", "shot", "drink", "beverage", "drunk", "alcohol", "scotch", "liquor", "tumbler", "whisky", "booze", "glass"]}, "cup-with-straw": {"a": "cup with straw", "b": "1F964", "j": ["malt", "drink", "water", "soft drink", "soda", "juice"]}, "bubble-tea": {"a": "bubble tea", "b": "1F9CB", "j": ["bubble", "boba", "tea", "milk", "pearl", "straw", "milk tea", "taiwan"]}, "beverage-box": {"a": "beverage box", "b": "1F9C3", "j": ["beverage", "drink", "sweet", "straw", "juice", "box"]}, "mate": {"a": "mate", "b": "1F9C9", "j": ["drink", "beverage", "tea"]}, "ice": {"a": "ice", "b": "1F9CA", "j": ["iceberg", "cold", "ice cube", "water"]}, "chopsticks": {"a": "chopsticks", "b": "1F962", "j": ["food", "jeotgarak", "kuaizi", "hashi"]}, "fork-and-knife-with-plate": {"a": "fork and knife with plate", "b": "1F37D", "j": ["meal", "plate", "dinner", "knife", "eat", "lunch", "cooking", "food", "restaurant", "fork"]}, "fork-and-knife": {"a": "fork and knife", "b": "1F374", "j": ["knife", "cutlery", "cooking", "kitchen", "fork"]}, "spoon": {"a": "spoon", "b": "1F944", "j": ["kitchen", "tableware", "cutlery"]}, "kitchen-knife": {"a": "kitchen knife", "b": "1F52A", "j": ["hocho", "knife", "cutlery", "cooking", "weapon", "blade", "kitchen", "tool"]}, "amphora": {"a": "amphora", "b": "1F3FA", "j": ["Aquarius", "jar", "drink", "cooking", "zodiac", "vase", "jug"]}, "globe-showing-europeafrica": {"a": "globe showing Europe-Africa", "b": "1F30D", "j": ["Africa", "globe", "earth", "international", "Europe", "globe_showing_europe_africa", "world"]}, "globe-showing-americas": {"a": "globe showing Americas", "b": "1F30E", "j": ["globe", "earth", "international", "USA", "Americas", "world"]}, "globe-showing-asiaaustralia": {"a": "globe showing Asia-Australia", "b": "1F30F", "j": ["Asia", "globe", "earth", "globe_showing_asia_australia", "east", "Australia", "international", "world"]}, "globe-with-meridians": {"a": "globe with meridians", "b": "1F310", "j": ["meridians", "globe", "earth", "i18n", "international", "world", "internet", "interweb"]}, "world-map": {"a": "world map", "b": "1F5FA", "j": ["map", "location", "world", "direction"]}, "map-of-japan": {"a": "map of Japan", "b": "1F5FE", "j": ["asia", "map", "japanese", "country", "nation", "Japan"]}, "compass": {"a": "compass", "b": "1F9ED", "j": ["orienteering", "navigation", "magnetic"]}, "snowcapped-mountain": {"a": "snow-capped mountain", "b": "1F3D4", "j": ["snow", "mountain", "environment", "cold", "winter", "nature", "photo", "snow_capped_mountain"]}, "mountain": {"a": "mountain", "b": "26F0", "j": ["environment", "photo", "nature"]}, "volcano": {"a": "volcano", "b": "1F30B", "j": ["mountain", "eruption", "disaster", "nature", "photo"]}, "mount-fuji": {"a": "mount fuji", "b": "1F5FB", "j": ["fuji", "mountain", "japanese", "nature", "photo"]}, "camping": {"a": "camping", "b": "1F3D5", "j": ["outdoors", "photo", "tent"]}, "beach-with-umbrella": {"a": "beach with umbrella", "b": "1F3D6", "j": ["sand", "weather", "umbrella", "mojito", "summer", "sunny", "beach"]}, "desert": {"a": "desert", "b": "1F3DC", "j": ["warm", "saharah", "photo"]}, "desert-island": {"a": "desert island", "b": "1F3DD", "j": ["desert", "photo", "mojito", "tropical", "island"]}, "national-park": {"a": "national park", "b": "1F3DE", "j": ["environment", "park", "photo", "nature"]}, "stadium": {"a": "stadium", "b": "1F3DF", "j": ["venue", "sports", "place", "photo", "concert"]}, "classical-building": {"a": "classical building", "b": "1F3DB", "j": ["classical", "history", "culture", "art"]}, "building-construction": {"a": "building construction", "b": "1F3D7", "j": ["progress", "working", "wip", "construction"]}, "brick": {"a": "brick", "b": "1F9F1", "j": ["bricks", "wall", "clay", "mortar"]}, "rock": {"a": "rock", "b": "1FAA8", "j": ["stone", "heavy", "boulder", "solid"]}, "wood": {"a": "wood", "b": "1FAB5", "j": ["timber", "trunk", "log", "lumber", "nature"]}, "hut": {"a": "hut", "b": "1F6D6", "j": ["house", "structure", "yurt", "roundhouse"]}, "houses": {"a": "houses", "b": "1F3D8", "j": ["photo", "buildings"]}, "derelict-house": {"a": "derelict house", "b": "1F3DA", "j": ["abandon", "house", "broken", "building", "derelict", "evict"]}, "house": {"a": "house", "b": "1F3E0", "j": ["building", "home"]}, "house-with-garden": {"a": "house with garden", "b": "1F3E1", "j": ["garden", "house", "nature", "plant", "home"]}, "office-building": {"a": "office building", "b": "1F3E2", "j": ["bureau", "work", "building"]}, "japanese-post-office": {"a": "Japanese post office", "b": "1F3E3", "j": ["envelope", "communication", "Japanese", "building", "post"]}, "post-office": {"a": "post office", "b": "1F3E4", "j": ["email", "post", "European", "building"]}, "hospital": {"a": "hospital", "b": "1F3E5", "j": ["surgery", "building", "doctor", "medicine", "health"]}, "bank": {"a": "bank", "b": "1F3E6", "j": ["cash", "business", "building", "enterprise", "money", "sales"]}, "hotel": {"a": "hotel", "b": "1F3E8", "j": ["checkin", "accomodation", "building"]}, "love-hotel": {"a": "love hotel", "b": "1F3E9", "j": ["like", "dating", "love", "affection", "hotel"]}, "convenience-store": {"a": "convenience store", "b": "1F3EA", "j": ["shopping", "groceries", "building", "store", "convenience"]}, "school": {"a": "school", "b": "1F3EB", "j": ["learn", "building", "education", "student", "teach"]}, "department-store": {"a": "department store", "b": "1F3EC", "j": ["shopping", "department", "mall", "building", "store"]}, "factory": {"a": "factory", "b": "1F3ED", "j": ["smoke", "pollution", "building", "industry"]}, "japanese-castle": {"a": "Japanese castle", "b": "1F3EF", "j": ["castle", "Japanese", "photo", "building"]}, "castle": {"a": "castle", "b": "1F3F0", "j": ["history", "royalty", "European", "building"]}, "wedding": {"a": "wedding", "b": "1F492", "j": ["love", "like", "marriage", "bride", "groom", "romance", "chapel", "affection", "couple"]}, "tokyo-tower": {"a": "Tokyo tower", "b": "1F5FC", "j": ["japanese", "photo", "Tokyo", "tower"]}, "statue-of-liberty": {"a": "Statue of Liberty", "b": "1F5FD", "j": ["newyork", "liberty", "statue", "american"]}, "church": {"a": "church", "b": "26EA", "j": ["cross", "Christian", "building", "christ", "religion"]}, "mosque": {"a": "mosque", "b": "1F54C", "j": ["minaret", "worship", "islam", "Muslim", "religion"]}, "hindu-temple": {"a": "hindu temple", "b": "1F6D5", "j": ["hindu", "temple", "religion"]}, "synagogue": {"a": "synagogue", "b": "1F54D", "j": ["jewish", "Jew", "worship", "Jewish", "judaism", "temple", "religion"]}, "shinto-shrine": {"a": "shinto shrine", "b": "26E9", "j": ["kyoto", "shinto", "temple", "religion", "shrine", "japan"]}, "kaaba": {"a": "kaaba", "b": "1F54B", "j": ["mecca", "mosque", "islam", "Muslim", "religion"]}, "fountain": {"a": "fountain", "b": "26F2", "j": ["fresh", "photo", "summer", "water"]}, "tent": {"a": "tent", "b": "26FA", "j": ["photo", "outdoors", "camping"]}, "foggy": {"a": "foggy", "b": "1F301", "j": ["fog", "mountain", "photo"]}, "night-with-stars": {"a": "night with stars", "b": "1F303", "j": ["night", "downtown", "evening", "star", "city"]}, "cityscape": {"a": "cityscape", "b": "1F3D9", "j": ["night life", "urban", "city", "photo"]}, "sunrise-over-mountains": {"a": "sunrise over mountains", "b": "1F304", "j": ["sun", "sunrise", "mountain", "vacation", "morning", "view", "photo"]}, "sunrise": {"a": "sunrise", "b": "1F305", "j": ["sun", "vacation", "morning", "photo", "view"]}, "cityscape-at-dusk": {"a": "cityscape at dusk", "b": "1F306", "j": ["evening", "sunset", "city", "landscape", "sky", "dusk", "photo", "buildings"]}, "sunset": {"a": "sunset", "b": "1F307", "j": ["sun", "good morning", "dawn", "dusk", "photo"]}, "bridge-at-night": {"a": "bridge at night", "b": "1F309", "j": ["night", "sanfrancisco", "photo", "bridge"]}, "hot-springs": {"a": "hot springs", "b": "2668", "j": ["bath", "springs", "hot", "steaming", "relax", "warm", "hotsprings"]}, "carousel-horse": {"a": "carousel horse", "b": "1F3A0", "j": ["carnival", "carousel", "photo", "horse"]}, "ferris-wheel": {"a": "ferris wheel", "b": "1F3A1", "j": ["wheel", "carnival", "photo", "londoneye", "ferris", "amusement park"]}, "roller-coaster": {"a": "roller coaster", "b": "1F3A2", "j": ["coaster", "playground", "carnival", "photo", "fun", "roller", "amusement park"]}, "barber-pole": {"a": "barber pole", "b": "1F488", "j": ["salon", "hair", "style", "barber", "pole", "haircut"]}, "circus-tent": {"a": "circus tent", "b": "1F3AA", "j": ["festival", "carnival", "tent", "party", "circus"]}, "locomotive": {"a": "locomotive", "b": "1F682", "j": ["railway", "vehicle", "transportation", "engine", "train", "steam"]}, "railway-car": {"a": "railway car", "b": "1F683", "j": ["railway", "vehicle", "electric", "transportation", "tram", "trolleybus", "train", "car"]}, "highspeed-train": {"a": "high-speed train", "b": "1F684", "j": ["railway", "vehicle", "transportation", "speed", "high_speed_train", "train", "shinkansen"]}, "bullet-train": {"a": "bullet train", "b": "1F685", "j": ["railway", "vehicle", "bullet", "transportation", "fast", "speed", "travel", "shinkansen", "train", "public"]}, "train": {"a": "train", "b": "1F686", "j": ["railway", "vehicle", "transportation"]}, "metro": {"a": "metro", "b": "1F687", "j": ["subway", "transportation", "mrt", "blue-square", "underground", "tube"]}, "light-rail": {"a": "light rail", "b": "1F688", "j": ["railway", "vehicle", "transportation"]}, "station": {"a": "station", "b": "1F689", "j": ["railway", "vehicle", "transportation", "train", "public"]}, "tram": {"a": "tram", "b": "1F68A", "j": ["vehicle", "transportation", "trolleybus"]}, "monorail": {"a": "monorail", "b": "1F69D", "j": ["vehicle", "transportation"]}, "mountain-railway": {"a": "mountain railway", "b": "1F69E", "j": ["railway", "vehicle", "mountain", "transportation", "car"]}, "tram-car": {"a": "tram car", "b": "1F68B", "j": ["vehicle", "car", "transportation", "carriage", "trolleybus", "tram", "travel", "public"]}, "bus": {"a": "bus", "b": "1F68C", "j": ["vehicle", "transportation", "car"]}, "oncoming-bus": {"a": "oncoming bus", "b": "1F68D", "j": ["transportation", "vehicle", "oncoming", "bus"]}, "trolleybus": {"a": "trolleybus", "b": "1F68E", "j": ["vehicle", "bus", "transportation", "tram", "trolley", "bart"]}, "minibus": {"a": "minibus", "b": "1F690", "j": ["bus", "vehicle", "transportation", "car"]}, "ambulance": {"a": "ambulance", "b": "1F691", "j": ["hospital", "vehicle", "911", "health"]}, "fire-engine": {"a": "fire engine", "b": "1F692", "j": ["vehicle", "truck", "transportation", "engine", "cars", "fire"]}, "police-car": {"a": "police car", "b": "1F693", "j": ["vehicle", "police", "law", "transportation", "enforcement", "cars", "legal", "patrol", "car"]}, "oncoming-police-car": {"a": "oncoming police car", "b": "1F694", "j": ["vehicle", "police", "law", "oncoming", "enforcement", "911", "legal", "car"]}, "taxi": {"a": "taxi", "b": "1F695", "j": ["vehicle", "transportation", "cars", "uber"]}, "oncoming-taxi": {"a": "oncoming taxi", "b": "1F696", "j": ["vehicle", "oncoming", "cars", "uber", "taxi"]}, "automobile": {"a": "automobile", "b": "1F697", "j": ["vehicle", "transportation", "car", "red"]}, "oncoming-automobile": {"a": "oncoming automobile", "b": "1F698", "j": ["vehicle", "oncoming", "transportation", "automobile", "car"]}, "sport-utility-vehicle": {"a": "sport utility vehicle", "b": "1F699", "j": ["recreational", "vehicle", "sport utility", "transportation"]}, "pickup-truck": {"a": "pickup truck", "b": "1F6FB", "j": ["truck", "transportation", "pick-up", "pickup", "car"]}, "delivery-truck": {"a": "delivery truck", "b": "1F69A", "j": ["delivery", "transportation", "truck", "cars"]}, "articulated-lorry": {"a": "articulated lorry", "b": "1F69B", "j": ["vehicle", "truck", "lorry", "transportation", "express", "semi", "cars"]}, "tractor": {"a": "tractor", "b": "1F69C", "j": ["vehicle", "agriculture", "farming", "car"]}, "racing-car": {"a": "racing car", "b": "1F3CE", "j": ["formula", "f1", "fast", "sports", "race", "racing", "car"]}, "motorcycle": {"a": "motorcycle", "b": "1F3CD", "j": ["fast", "race", "sports", "racing"]}, "motor-scooter": {"a": "motor scooter", "b": "1F6F5", "j": ["vehicle", "scooter", "vespa", "sasha", "motor"]}, "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", "transportation", "move"]}, "bicycle": {"a": "bicycle", "b": "1F6B2", "j": ["bike", "hipster", "exercise", "sports"]}, "kick-scooter": {"a": "kick scooter", "b": "1F6F4", "j": ["scooter", "razor", "vehicle", "kick"]}, "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", "stop", "transportation", "busstop", "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": ["barrell", "drum", "oil"]}, "fuel-pump": {"a": "fuel pump", "b": "26FD", "j": ["petroleum", "gas", "fuel", "pump", "fuelpump", "station", "gas station", "diesel"]}, "police-car-light": {"a": "police car light", "b": "1F6A8", "j": ["revolving", "beacon", "police", "alert", "law", "pinged", "emergency", "light", "ambulance", "911", "legal", "error", "car"]}, "horizontal-traffic-light": {"a": "horizontal traffic light", "b": "1F6A5", "j": ["transportation", "traffic", "light", "signal"]}, "vertical-traffic-light": {"a": "vertical traffic light", "b": "1F6A6", "j": ["traffic", "transportation", "light", "driving", "signal"]}, "stop-sign": {"a": "stop sign", "b": "1F6D1", "j": ["octagonal", "sign", "stop"]}, "construction": {"a": "construction", "b": "1F6A7", "j": ["warning", "wip", "barrier", "progress", "caution"]}, "anchor": {"a": "anchor", "b": "2693", "j": ["boat", "sea", "ferry", "ship", "tool"]}, "sailboat": {"a": "sailboat", "b": "26F5", "j": ["yacht", "resort", "boat", "sailing", "sea", "transportation", "water", "ship", "summer"]}, "canoe": {"a": "canoe", "b": "1F6F6", "j": ["paddle", "ship", "water", "boat"]}, "speedboat": {"a": "speedboat", "b": "1F6A4", "j": ["vehicle", "boat", "transportation", "ship", "summer"]}, "passenger-ship": {"a": "passenger ship", "b": "1F6F3", "j": ["yacht", "passenger", "cruise", "ferry", "ship"]}, "ferry": {"a": "ferry", "b": "26F4", "j": ["yacht", "ship", "passenger", "boat"]}, "motor-boat": {"a": "motor boat", "b": "1F6E5", "j": ["ship", "motorboat", "boat"]}, "ship": {"a": "ship", "b": "1F6A2", "j": ["titanic", "passenger", "boat", "transportation", "deploy"]}, "airplane": {"a": "airplane", "b": "2708", "j": ["vehicle", "flight", "transportation", "fly", "aeroplane"]}, "small-airplane": {"a": "small airplane", "b": "1F6E9", "j": ["vehicle", "flight", "transportation", "fly", "airplane", "aeroplane"]}, "airplane-departure": {"a": "airplane departure", "b": "1F6EB", "j": ["departure", "flight", "check-in", "airport", "airplane", "departures", "aeroplane", "landing"]}, "airplane-arrival": {"a": "airplane arrival", "b": "1F6EC", "j": ["arrivals", "arriving", "flight", "boarding", "airport", "airplane", "aeroplane", "landing"]}, "parachute": {"a": "parachute", "b": "1FA82", "j": ["skydive", "hang-glide", "fly", "parasail", "glide"]}, "seat": {"a": "seat", "b": "1F4BA", "j": ["bus", "transport", "flight", "chair", "sit", "fly", "airplane"]}, "helicopter": {"a": "helicopter", "b": "1F681", "j": ["vehicle", "transportation", "fly"]}, "suspension-railway": {"a": "suspension railway", "b": "1F69F", "j": ["railway", "vehicle", "transportation", "suspension"]}, "mountain-cableway": {"a": "mountain cableway", "b": "1F6A0", "j": ["vehicle", "mountain", "transportation", "ski", "gondola", "cable"]}, "aerial-tramway": {"a": "aerial tramway", "b": "1F6A1", "j": ["vehicle", "aerial", "transportation", "ski", "gondola", "cable", "tramway", "car"]}, "satellite": {"a": "satellite", "b": "1F6F0", "j": ["communication", "gps", "ISS", "space", "NASA", "orbit", "spaceflight"]}, "rocket": {"a": "rocket", "b": "1F680", "j": ["outer space", "launch", "NASA", "ship", "space", "staffmode", "fly", "outer_space"]}, "flying-saucer": {"a": "flying saucer", "b": "1F6F8", "j": ["transportation", "vehicle", "ufo", "UFO"]}, "bellhop-bell": {"a": "bellhop bell", "b": "1F6CE", "j": ["service", "bellhop", "hotel", "bell"]}, "luggage": {"a": "luggage", "b": "1F9F3", "j": ["packing", "travel"]}, "hourglass-done": {"a": "hourglass done", "b": "231B", "j": ["test", "quiz", "timer", "sand", "limit", "oldschool", "exam", "clock", "time"]}, "hourglass-not-done": {"a": "hourglass not done", "b": "23F3", "j": ["hourglass", "timer", "sand", "oldschool", "countdown", "time"]}, "watch": {"a": "watch", "b": "231A", "j": ["accessories", "clock", "time"]}, "alarm-clock": {"a": "alarm clock", "b": "23F0", "j": ["alarm", "clock", "wake", "time"]}, "stopwatch": {"a": "stopwatch", "b": "23F1", "j": ["clock", "time", "deadline"]}, "timer-clock": {"a": "timer clock", "b": "23F2", "j": ["clock", "alarm", "timer"]}, "mantelpiece-clock": {"a": "mantelpiece clock", "b": "1F570", "j": ["clock", "time"]}, "twelve-oclock": {"a": "twelve o’clock", "b": "1F55B", "j": ["midnight", "12", "12:00", "twelve", "midday", "noon", "late", "o’clock", "early", "twelve_o_clock", "schedule", "00", "clock", "time"]}, "twelvethirty": {"a": "twelve-thirty", "b": "1F567", "j": ["12", "twelve_thirty", "thirty", "twelve", "late", "early", "schedule", "12:30", "clock", "time"]}, "one-oclock": {"a": "one o’clock", "b": "1F550", "j": ["1:00", "1", "late", "time", "early", "o’clock", "schedule", "00", "one_o_clock", "clock", "one"]}, "onethirty": {"a": "one-thirty", "b": "1F55C", "j": ["1", "1:30", "thirty", "late", "time", "early", "schedule", "one_thirty", "clock", "one"]}, "two-oclock": {"a": "two o’clock", "b": "1F551", "j": ["two_o_clock", "2", "2:00", "late", "early", "schedule", "o’clock", "00", "clock", "two", "time"]}, "twothirty": {"a": "two-thirty", "b": "1F55D", "j": ["two_thirty", "2", "thirty", "late", "2:30", "early", "schedule", "clock", "two", "time"]}, "three-oclock": {"a": "three o’clock", "b": "1F552", "j": ["3:00", "three_o_clock", "late", "3", "early", "o’clock", "three", "schedule", "00", "clock", "time"]}, "threethirty": {"a": "three-thirty", "b": "1F55E", "j": ["thirty", "late", "3", "3:30", "three", "three_thirty", "early", "schedule", "clock", "time"]}, "four-oclock": {"a": "four o’clock", "b": "1F553", "j": ["four", "late", "early", "schedule", "o’clock", "4", "00", "four_o_clock", "clock", "4:00", "time"]}, "fourthirty": {"a": "four-thirty", "b": "1F55F", "j": ["thirty", "four", "late", "4:30", "early", "schedule", "four_thirty", "4", "clock", "time"]}, "five-oclock": {"a": "five o’clock", "b": "1F554", "j": ["five_o_clock", "late", "early", "schedule", "o’clock", "five", "00", "5:00", "5", "clock", "time"]}, "fivethirty": {"a": "five-thirty", "b": "1F560", "j": ["five_thirty", "thirty", "late", "early", "schedule", "five", "5", "clock", "5:30", "time"]}, "six-oclock": {"a": "six o’clock", "b": "1F555", "j": ["late", "early", "schedule", "o’clock", "six_o_clock", "6", "dawn", "00", "dusk", "6:00", "clock", "six", "time"]}, "sixthirty": {"a": "six-thirty", "b": "1F561", "j": ["thirty", "6:30", "late", "early", "schedule", "6", "six_thirty", "clock", "six", "time"]}, "seven-oclock": {"a": "seven o’clock", "b": "1F556", "j": ["7:00", "7", "seven_o_clock", "late", "early", "schedule", "o’clock", "seven", "00", "clock", "time"]}, "seventhirty": {"a": "seven-thirty", "b": "1F562", "j": ["seven_thirty", "7", "thirty", "7:30", "late", "early", "schedule", "seven", "clock", "time"]}, "eight-oclock": {"a": "eight o’clock", "b": "1F557", "j": ["8:00", "eight", "late", "o’clock", "early", "schedule", "00", "eight_o_clock", "clock", "8", "time"]}, "eightthirty": {"a": "eight-thirty", "b": "1F563", "j": ["8:30", "thirty", "eight_thirty", "eight", "late", "early", "schedule", "clock", "8", "time"]}, "nine-oclock": {"a": "nine o’clock", "b": "1F558", "j": ["nine", "9:00", "9", "nine_o_clock", "late", "early", "schedule", "o’clock", "00", "clock", "time"]}, "ninethirty": {"a": "nine-thirty", "b": "1F564", "j": ["nine", "9", "thirty", "late", "nine_thirty", "early", "schedule", "9:30", "clock", "time"]}, "ten-oclock": {"a": "ten o’clock", "b": "1F559", "j": ["ten", "late", "early", "schedule", "o’clock", "10:00", "00", "10", "ten_o_clock", "clock", "time"]}, "tenthirty": {"a": "ten-thirty", "b": "1F565", "j": ["10:30", "thirty", "ten", "late", "early", "schedule", "ten_thirty", "10", "clock", "time"]}, "eleven-oclock": {"a": "eleven o’clock", "b": "1F55A", "j": ["eleven_o_clock", "eleven", "late", "early", "schedule", "o’clock", "00", "11", "clock", "11:00", "time"]}, "eleventhirty": {"a": "eleven-thirty", "b": "1F566", "j": ["thirty", "eleven", "eleven_thirty", "late", "early", "schedule", "11:30", "11", "clock", "time"]}, "new-moon": {"a": "new moon", "b": "1F311", "j": ["night", "sleep", "evening", "moon", "nature", "space", "planet", "dark", "twilight"]}, "waxing-crescent-moon": {"a": "waxing crescent moon", "b": "1F312", "j": ["night", "sleep", "evening", "crescent", "moon", "nature", "space", "planet", "waxing", "twilight"]}, "first-quarter-moon": {"a": "first quarter moon", "b": "1F313", "j": ["night", "sleep", "evening", "moon", "nature", "space", "planet", "quarter", "twilight"]}, "waxing-gibbous-moon": {"a": "waxing gibbous moon", "b": "1F314", "j": ["night", "sleep", "evening", "moon", "nature", "sky", "space", "planet", "gibbous", "gray", "waxing", "twilight"]}, "full-moon": {"a": "full moon", "b": "1F315", "j": ["night", "full", "sleep", "evening", "moon", "nature", "space", "yellow", "planet", "twilight"]}, "waning-gibbous-moon": {"a": "waning gibbous moon", "b": "1F316", "j": ["night", "sleep", "waning", "evening", "moon", "nature", "waxing_gibbous_moon", "space", "planet", "gibbous", "twilight"]}, "last-quarter-moon": {"a": "last quarter moon", "b": "1F317", "j": ["night", "sleep", "evening", "moon", "nature", "space", "planet", "quarter", "twilight"]}, "waning-crescent-moon": {"a": "waning crescent moon", "b": "1F318", "j": ["night", "sleep", "waning", "evening", "crescent", "moon", "nature", "space", "planet", "twilight"]}, "crescent-moon": {"a": "crescent moon", "b": "1F319", "j": ["night", "sleep", "evening", "crescent", "moon", "sky", "magic"]}, "new-moon-face": {"a": "new moon face", "b": "1F31A", "j": ["night", "sleep", "evening", "moon", "face", "nature", "space", "planet", "twilight"]}, "first-quarter-moon-face": {"a": "first quarter moon face", "b": "1F31B", "j": ["night", "sleep", "evening", "moon", "face", "nature", "space", "planet", "quarter", "twilight"]}, "last-quarter-moon-face": {"a": "last quarter moon face", "b": "1F31C", "j": ["night", "sleep", "evening", "moon", "face", "nature", "space", "planet", "quarter", "twilight"]}, "thermometer": {"a": "thermometer", "b": "1F321", "j": ["weather", "hot", "cold", "temperature"]}, "sun": {"a": "sun", "b": "2600", "j": ["brightness", "rays", "spring", "bright", "nature", "weather", "summer", "sunny", "beach"]}, "full-moon-face": {"a": "full moon face", "b": "1F31D", "j": ["night", "full", "sleep", "evening", "moon", "bright", "face", "nature", "space", "planet", "twilight"]}, "sun-with-face": {"a": "sun with face", "b": "1F31E", "j": ["sun", "bright", "face", "nature", "sky", "morning"]}, "ringed-planet": {"a": "ringed planet", "b": "1FA90", "j": ["outerspace", "saturnine", "saturn"]}, "star": {"a": "star", "b": "2B50", "j": ["night", "yellow"]}, "glowing-star": {"a": "glowing star", "b": "1F31F", "j": ["awesome", "night", "shining", "star", "good", "glittery", "glow", "sparkle", "magic"]}, "shooting-star": {"a": "shooting star", "b": "1F320", "j": ["shooting", "night", "star", "falling", "photo"]}, "milky-way": {"a": "milky way", "b": "1F30C", "j": ["stars", "space", "photo"]}, "cloud": {"a": "cloud", "b": "2601", "j": ["weather", "sky"]}, "sun-behind-cloud": {"a": "sun behind cloud", "b": "26C5", "j": ["sun", "spring", "cloud", "nature", "weather", "morning", "fall", "cloudy"]}, "cloud-with-lightning-and-rain": {"a": "cloud with lightning and rain", "b": "26C8", "j": ["lightning", "rain", "cloud", "weather", "thunder"]}, "sun-behind-small-cloud": {"a": "sun behind small cloud", "b": "1F324", "j": ["sun", "weather", "cloud"]}, "sun-behind-large-cloud": {"a": "sun behind large cloud", "b": "1F325", "j": ["sun", "weather", "cloud"]}, "sun-behind-rain-cloud": {"a": "sun behind rain cloud", "b": "1F326", "j": ["sun", "weather", "rain", "cloud"]}, "cloud-with-rain": {"a": "cloud with rain", "b": "1F327", "j": ["weather", "rain", "cloud"]}, "cloud-with-snow": {"a": "cloud with snow", "b": "1F328", "j": ["weather", "snow", "cold", "cloud"]}, "cloud-with-lightning": {"a": "cloud with lightning", "b": "1F329", "j": ["weather", "lightning", "thunder", "cloud"]}, "tornado": {"a": "tornado", "b": "1F32A", "j": ["cyclone", "cloud", "weather", "twister", "whirlwind"]}, "fog": {"a": "fog", "b": "1F32B", "j": ["weather", "cloud"]}, "wind-face": {"a": "wind face", "b": "1F32C", "j": ["air", "blow", "face", "cloud", "gust", "wind"]}, "cyclone": {"a": "cyclone", "b": "1F300", "j": ["blue", "spin", "whirlpool", "typhoon", "cloud", "weather", "twister", "spiral", "dizzy", "hurricane", "vortex", "swirl", "tornado"]}, "rainbow": {"a": "rainbow", "b": "1F308", "j": ["rain", "spring", "nature", "sky", "photo", "unicorn_face", "happy"]}, "closed-umbrella": {"a": "closed umbrella", "b": "1F302", "j": ["rain", "umbrella", "weather", "clothing", "drizzle"]}, "umbrella": {"a": "umbrella", "b": "2602", "j": ["weather", "clothing", "rain", "spring"]}, "umbrella-with-rain-drops": {"a": "umbrella with rain drops", "b": "2614", "j": ["drop", "rain", "spring", "rainy", "umbrella", "clothing", "weather"]}, "umbrella-on-ground": {"a": "umbrella on ground", "b": "26F1", "j": ["sun", "rain", "umbrella", "weather", "summer"]}, "high-voltage": {"a": "high voltage", "b": "26A1", "j": ["danger", "lightning", "electric", "lightning bolt", "zap", "fast", "weather", "thunder", "voltage"]}, "snowflake": {"a": "snowflake", "b": "2744", "j": ["snow", "cold", "xmas", "winter", "season", "weather", "christmas"]}, "snowman": {"a": "snowman", "b": "2603", "j": ["snow", "cold", "winter", "xmas", "season", "weather", "frozen", "christmas"]}, "snowman-without-snow": {"a": "snowman without snow", "b": "26C4", "j": ["snow", "snowman", "cold", "winter", "xmas", "season", "weather", "frozen", "without_snow", "christmas"]}, "comet": {"a": "comet", "b": "2604", "j": ["space"]}, "fire": {"a": "fire", "b": "1F525", "j": ["flame", "cook", "tool", "hot"]}, "droplet": {"a": "droplet", "b": "1F4A7", "j": ["drop", "sweat", "drip", "spring", "cold", "water", "comic", "faucet"]}, "water-wave": {"a": "water wave", "b": "1F30A", "j": ["wave", "tsunami", "disaster", "sea", "water", "nature", "ocean"]}, "jackolantern": {"a": "jack-o-lantern", "b": "1F383", "j": ["jack_o_lantern", "creepy", "celebration", "halloween", "pumpkin", "light", "fall", "jack", "lantern"]}, "christmas-tree": {"a": "Christmas tree", "b": "1F384", "j": ["vacation", "festival", "xmas", "celebration", "Christmas", "december", "tree"]}, "fireworks": {"a": "fireworks", "b": "1F386", "j": ["congratulations", "festival", "celebration", "carnival", "photo"]}, "sparkler": {"a": "sparkler", "b": "1F387", "j": ["night", "fireworks", "shine", "stars", "celebration", "sparkle"]}, "firecracker": {"a": "firecracker", "b": "1F9E8", "j": ["boom", "fireworks", "explode", "explosion", "explosive", "dynamite"]}, "sparkles": {"a": "sparkles", "b": "2728", "j": ["awesome", "shiny", "cool", "shine", "star", "stars", "*", "good", "sparkle", "magic"]}, "balloon": {"a": "balloon", "b": "1F388", "j": ["birthday", "circus", "celebration", "party"]}, "party-popper": {"a": "party popper", "b": "1F389", "j": ["birthday", "tada", "congratulations", "popper", "celebration", "party", "circus", "magic"]}, "confetti-ball": {"a": "confetti ball", "b": "1F38A", "j": ["birthday", "festival", "ball", "celebration", "party", "confetti", "circus"]}, "tanabata-tree": {"a": "tanabata tree", "b": "1F38B", "j": ["banner", "Japanese", "nature", "celebration", "plant", "summer", "tree", "branch"]}, "pine-decoration": {"a": "pine decoration", "b": "1F38D", "j": ["bamboo", "panda", "pine", "Japanese", "nature", "celebration", "plant", "vegetable"]}, "japanese-dolls": {"a": "Japanese dolls", "b": "1F38E", "j": ["toy", "Japanese", "festival", "japanese", "celebration", "kimono", "doll"]}, "carp-streamer": {"a": "carp streamer", "b": "1F38F", "j": ["carp", "banner", "japanese", "fish", "celebration", "streamer", "koinobori"]}, "wind-chime": {"a": "wind chime", "b": "1F390", "j": ["ding", "spring", "chime", "bell", "celebration", "nature", "wind"]}, "moon-viewing-ceremony": {"a": "moon viewing ceremony", "b": "1F391", "j": ["tsukimi", "asia", "moon", "celebration", "ceremony", "photo", "japan"]}, "red-envelope": {"a": "red envelope", "b": "1F9E7", "j": ["hóngbāo", "lai see", "gift", "money", "good luck"]}, "ribbon": {"a": "ribbon", "b": "1F380", "j": ["bowtie", "pink", "girl", "decoration", "celebration"]}, "wrapped-gift": {"a": "wrapped gift", "b": "1F381", "j": ["birthday", "xmas", "celebration", "gift", "present", "wrapped", "box", "christmas"]}, "reminder-ribbon": {"a": "reminder ribbon", "b": "1F397", "j": ["cause", "support", "sports", "celebration", "reminder", "awareness", "ribbon"]}, "admission-tickets": {"a": "admission tickets", "b": "1F39F", "j": ["admission", "sports", "ticket", "entrance", "concert"]}, "ticket": {"a": "ticket", "b": "1F3AB", "j": ["admission", "event", "pass", "concert"]}, "military-medal": {"a": "military medal", "b": "1F396", "j": ["medal", "military", "award", "celebration", "army", "winning"]}, "trophy": {"a": "trophy", "b": "1F3C6", "j": ["ftw", "award", "contest", "place", "ceremony", "win", "prize"]}, "sports-medal": {"a": "sports medal", "b": "1F3C5", "j": ["medal", "winning", "award"]}, "1st-place-medal": {"a": "1st place medal", "b": "1F947", "j": ["medal", "award", "first", "gold", "winning"]}, "2nd-place-medal": {"a": "2nd place medal", "b": "1F948", "j": ["second", "medal", "award", "silver"]}, "3rd-place-medal": {"a": "3rd place medal", "b": "1F949", "j": ["medal", "award", "third", "bronze"]}, "soccer-ball": {"a": "soccer ball", "b": "26BD", "j": ["sports", "soccer", "football", "ball"]}, "baseball": {"a": "baseball", "b": "26BE", "j": ["balls", "sports", "ball"]}, "softball": {"a": "softball", "b": "1F94E", "j": ["underarm", "sports", "ball", "balls", "glove"]}, "basketball": {"a": "basketball", "b": "1F3C0", "j": ["hoop", "sports", "ball", "balls", "NBA"]}, "volleyball": {"a": "volleyball", "b": "1F3D0", "j": ["game", "balls", "sports", "ball"]}, "american-football": {"a": "american football", "b": "1F3C8", "j": ["sports", "NFL", "ball", "balls", "american", "football"]}, "rugby-football": {"a": "rugby football", "b": "1F3C9", "j": ["team", "sports", "ball", "rugby", "football"]}, "tennis": {"a": "tennis", "b": "1F3BE", "j": ["racquet", "sports", "ball", "balls", "green"]}, "flying-disc": {"a": "flying disc", "b": "1F94F", "j": ["frisbee", "ultimate", "sports"]}, "bowling": {"a": "bowling", "b": "1F3B3", "j": ["play", "sports", "ball", "game", "fun"]}, "cricket-game": {"a": "cricket game", "b": "1F3CF", "j": ["ball", "game", "bat", "sports"]}, "field-hockey": {"a": "field hockey", "b": "1F3D1", "j": ["stick", "ball", "sports", "field", "game", "hockey"]}, "ice-hockey": {"a": "ice hockey", "b": "1F3D2", "j": ["stick", "hockey", "ice", "sports", "game", "puck"]}, "lacrosse": {"a": "lacrosse", "b": "1F94D", "j": ["stick", "goal", "sports", "ball"]}, "ping-pong": {"a": "ping pong", "b": "1F3D3", "j": ["pingpong", "bat", "sports", "paddle", "ball", "game", "table tennis"]}, "badminton": {"a": "badminton", "b": "1F3F8", "j": ["shuttlecock", "racquet", "sports", "game", "birdie"]}, "boxing-glove": {"a": "boxing glove", "b": "1F94A", "j": ["boxing", "fighting", "sports", "glove"]}, "martial-arts-uniform": {"a": "martial arts uniform", "b": "1F94B", "j": ["judo", "martial arts", "taekwondo", "uniform", "karate"]}, "goal-net": {"a": "goal net", "b": "1F945", "j": ["goal", "net", "sports"]}, "flag-in-hole": {"a": "flag in hole", "b": "26F3", "j": ["business", "flag", "golf", "sports", "summer", "hole"]}, "ice-skate": {"a": "ice skate", "b": "26F8", "j": ["ice", "skate", "sports"]}, "fishing-pole": {"a": "fishing pole", "b": "1F3A3", "j": ["hobby", "fish", "food", "pole", "summer"]}, "diving-mask": {"a": "diving mask", "b": "1F93F", "j": ["scuba", "diving", "sport", "ocean", "snorkeling"]}, "running-shirt": {"a": "running shirt", "b": "1F3BD", "j": ["sash", "shirt", "athletics", "play", "pageant", "running"]}, "skis": {"a": "skis", "b": "1F3BF", "j": ["snow", "cold", "winter", "sports", "ski"]}, "sled": {"a": "sled", "b": "1F6F7", "j": ["luge", "toboggan", "sledge", "sleigh"]}, "curling-stone": {"a": "curling stone", "b": "1F94C", "j": ["game", "rock", "sports"]}, "bullseye": {"a": "bullseye", "b": "1F3AF", "j": ["target", "direct hit", "play", "dart", "hit", "bar", "game", "direct_hit"]}, "yoyo": {"a": "yo-yo", "b": "1FA80", "j": ["fluctuate", "toy", "yo_yo"]}, "kite": {"a": "kite", "b": "1FA81", "j": ["soar", "wind", "fly"]}, "pool-8-ball": {"a": "pool 8 ball", "b": "1F3B1", "j": ["pool", "hobby", "billiard", "eight", "ball", "game", "luck", "8", "magic"]}, "crystal-ball": {"a": "crystal ball", "b": "1F52E", "j": ["disco", "tool", "crystal", "ball", "fairy tale", "fantasy", "party", "fortune_teller", "circus", "fortune", "magic"]}, "magic-wand": {"a": "magic wand", "b": "1FA84", "j": ["witch", "power", "supernature", "wizard", "magic"]}, "nazar-amulet": {"a": "nazar amulet", "b": "1F9FF", "j": ["talisman", "charm", "bead", "nazar", "evil-eye"]}, "video-game": {"a": "video game", "b": "1F3AE", "j": ["controller", "play", "game", "PS4", "console"]}, "joystick": {"a": "joystick", "b": "1F579", "j": ["play", "game", "video game"]}, "slot-machine": {"a": "slot machine", "b": "1F3B0", "j": ["slot", "gamble", "casino", "game", "luck", "fruit machine", "bet", "vegas"]}, "game-die": {"a": "game die", "b": "1F3B2", "j": ["random", "play", "dice", "game", "luck", "die", "tabletop"]}, "puzzle-piece": {"a": "puzzle piece", "b": "1F9E9", "j": ["puzzle", "interlocking", "clue", "piece", "jigsaw"]}, "teddy-bear": {"a": "teddy bear", "b": "1F9F8", "j": ["plush", "toy", "plaything", "stuffed"]}, "piata": {"a": "piñata", "b": "1FA85", "j": ["mexico", "celebration", "candy", "pinata", "party"]}, "nesting-dolls": {"a": "nesting dolls", "b": "1FA86", "j": ["nesting", "toy", "russia", "matryoshka", "doll"]}, "spade-suit": {"a": "spade suit", "b": "2660", "j": ["card", "cards", "suits", "game", "poker", "magic"]}, "heart-suit": {"a": "heart suit", "b": "2665", "j": ["card", "cards", "suits", "game", "poker", "magic"]}, "diamond-suit": {"a": "diamond suit", "b": "2666", "j": ["card", "cards", "suits", "game", "poker", "magic"]}, "club-suit": {"a": "club suit", "b": "2663", "j": ["card", "cards", "suits", "game", "poker", "magic"]}, "chess-pawn": {"a": "chess pawn", "b": "265F", "j": ["chess", "expendable", "dupe"]}, "joker": {"a": "joker", "b": "1F0CF", "j": ["play", "magic", "card", "game", "poker", "wildcard", "cards"]}, "mahjong-red-dragon": {"a": "mahjong red dragon", "b": "1F004", "j": ["play", "mahjong", "kanji", "game", "red", "chinese"]}, "flower-playing-cards": {"a": "flower playing cards", "b": "1F3B4", "j": ["flower", "sunset", "card", "Japanese", "playing", "game", "red"]}, "performing-arts": {"a": "performing arts", "b": "1F3AD", "j": ["theater", "acting", "mask", "performing", "drama", "art", "theatre"]}, "framed-picture": {"a": "framed picture", "b": "1F5BC", "j": ["picture", "photography", "painting", "frame", "museum", "art"]}, "artist-palette": {"a": "artist palette", "b": "1F3A8", "j": ["draw", "design", "colors", "paint", "palette", "painting", "museum", "art"]}, "thread": {"a": "thread", "b": "1F9F5", "j": ["string", "needle", "spool", "sewing"]}, "sewing-needle": {"a": "sewing needle", "b": "1FAA1", "j": ["sutures", "stitches", "embroidery", "tailoring", "sewing", "needle"]}, "yarn": {"a": "yarn", "b": "1F9F6", "j": ["crochet", "knit", "ball"]}, "knot": {"a": "knot", "b": "1FAA2", "j": ["tangled", "twist", "rope", "tie", "twine", "scout"]}, "glasses": {"a": "glasses", "b": "1F453", "j": ["accessories", "nerdy", "geek", "eyeglasses", "eyesight", "clothing", "eye", "fashion", "eyewear", "dork"]}, "sunglasses": {"a": "sunglasses", "b": "1F576", "j": ["accessories", "glasses", "cool", "face", "eye", "eyewear", "dark"]}, "goggles": {"a": "goggles", "b": "1F97D", "j": ["welding", "swimming", "protection", "safety", "eye protection", "eyes"]}, "lab-coat": {"a": "lab coat", "b": "1F97C", "j": ["scientist", "chemist", "doctor", "experiment"]}, "safety-vest": {"a": "safety vest", "b": "1F9BA", "j": ["protection", "safety", "emergency", "vest"]}, "necktie": {"a": "necktie", "b": "1F454", "j": ["shirt", "business", "cloth", "formal", "tie", "clothing", "suitup", "fashion"]}, "tshirt": {"a": "t-shirt", "b": "1F455", "j": ["t_shirt", "shirt", "cloth", "clothing", "fashion", "tee", "casual"]}, "jeans": {"a": "jeans", "b": "1F456", "j": ["shopping", "trousers", "clothing", "fashion", "pants"]}, "scarf": {"a": "scarf", "b": "1F9E3", "j": ["neck", "winter", "clothes"]}, "gloves": {"a": "gloves", "b": "1F9E4", "j": ["hands", "hand", "winter", "clothes"]}, "coat": {"a": "coat", "b": "1F9E5", "j": ["jacket"]}, "socks": {"a": "socks", "b": "1F9E6", "j": ["stockings", "stocking", "clothes"]}, "dress": {"a": "dress", "b": "1F457", "j": ["shopping", "clothing", "fashion", "clothes"]}, "kimono": {"a": "kimono", "b": "1F458", "j": ["dress", "japanese", "clothing", "fashion", "women", "female"]}, "sari": {"a": "sari", "b": "1F97B", "j": ["dress", "clothing"]}, "onepiece-swimsuit": {"a": "one-piece swimsuit", "b": "1FA71", "j": ["one_piece_swimsuit", "fashion", "bathing suit"]}, "briefs": {"a": "briefs", "b": "1FA72", "j": ["swimsuit", "one-piece", "clothing", "underwear", "bathing suit"]}, "shorts": {"a": "shorts", "b": "1FA73", "j": ["clothing", "underwear", "pants", "bathing suit"]}, "bikini": {"a": "bikini", "b": "1F459", "j": ["swim", "woman", "girl", "swimming", "clothing", "fashion", "summer", "beach", "female"]}, "womans-clothes": {"a": "woman’s clothes", "b": "1F45A", "j": ["woman_s_clothes", "woman", "shopping_bags", "clothing", "fashion", "female"]}, "purse": {"a": "purse", "b": "1F45B", "j": ["accessories", "shopping", "clothing", "fashion", "money", "coin", "sales"]}, "handbag": {"a": "handbag", "b": "1F45C", "j": ["accessories", "shopping", "accessory", "clothing", "bag", "fashion", "purse"]}, "clutch-bag": {"a": "clutch bag", "b": "1F45D", "j": ["accessories", "shopping", "pouch", "clothing", "bag"]}, "shopping-bags": {"a": "shopping bags", "b": "1F6CD", "j": ["shopping", "mall", "buy", "bag", "purchase", "hotel"]}, "backpack": {"a": "backpack", "b": "1F392", "j": ["satchel", "school", "education", "student", "bag", "rucksack"]}, "thong-sandal": {"a": "thong sandal", "b": "1FA74", "j": ["beach sandals", "thong sandals", "zōri", "thongs", "sandals", "summer", "footwear"]}, "mans-shoe": {"a": "man’s shoe", "b": "1F45E", "j": ["man", "male", "man_s_shoe", "clothing", "fashion", "shoe"]}, "running-shoe": {"a": "running shoe", "b": "1F45F", "j": ["sneakers", "sneaker", "athletic", "shoes", "sports", "clothing", "shoe"]}, "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": ["woman", "pumps", "shoes", "heel", "high_heeled_shoe", "clothing", "fashion", "stiletto", "shoe", "female"]}, "womans-sandal": {"a": "woman’s sandal", "b": "1F461", "j": ["sandal", "woman", "shoes", "flip flops", "clothing", "woman_s_sandal", "fashion", "shoe"]}, "ballet-shoes": {"a": "ballet shoes", "b": "1FA70", "j": ["ballet", "dance"]}, "womans-boot": {"a": "woman’s boot", "b": "1F462", "j": ["woman", "shoes", "clothing", "fashion", "shoe", "boot", "woman_s_boot"]}, "crown": {"a": "crown", "b": "1F451", "j": ["royalty", "kod", "leader", "king", "clothing", "lord", "queen"]}, "womans-hat": {"a": "woman’s hat", "b": "1F452", "j": ["accessories", "spring", "woman", "lady", "woman_s_hat", "hat", "clothing", "fashion", "female"]}, "top-hat": {"a": "top hat", "b": "1F3A9", "j": ["classy", "top", "magic", "hat", "clothing", "tophat", "circus", "gentleman"]}, "graduation-cap": {"a": "graduation cap", "b": "1F393", "j": ["learn", "school", "hat", "college", "cap", "celebration", "clothing", "degree", "education", "legal", "university", "graduation"]}, "billed-cap": {"a": "billed cap", "b": "1F9E2", "j": ["cap", "baseball cap", "baseball"]}, "military-helmet": {"a": "military helmet", "b": "1FA96", "j": ["military", "warrior", "army", "helmet", "protection", "soldier"]}, "rescue-workers-helmet": {"a": "rescue worker’s helmet", "b": "26D1", "j": ["cross", "aid", "build", "construction", "face", "hat", "helmet", "rescue_worker_s_helmet"]}, "prayer-beads": {"a": "prayer beads", "b": "1F4FF", "j": ["dhikr", "religious", "beads", "clothing", "prayer", "necklace", "religion"]}, "lipstick": {"a": "lipstick", "b": "1F484", "j": ["woman", "girl", "makeup", "fashion", "cosmetics", "female"]}, "ring": {"a": "ring", "b": "1F48D", "j": ["wedding", "diamond", "marriage", "valentines", "propose", "fashion", "gem", "engagement", "jewelry"]}, "gem-stone": {"a": "gem stone", "b": "1F48E", "j": ["blue", "diamond", "gem", "ruby", "jewelry", "jewel"]}, "muted-speaker": {"a": "muted speaker", "b": "1F507", "j": ["silence", "speaker", "sound", "mute", "quiet", "silent", "volume"]}, "speaker-low-volume": {"a": "speaker low volume", "b": "1F508", "j": ["silence", "sound", "soft", "broadcast", "volume"]}, "speaker-medium-volume": {"a": "speaker medium volume", "b": "1F509", "j": ["speaker", "broadcast", "medium", "volume"]}, "speaker-high-volume": {"a": "speaker high volume", "b": "1F50A", "j": ["noisy", "noise", "speaker", "broadcast", "volume", "loud"]}, "loudspeaker": {"a": "loudspeaker", "b": "1F4E2", "j": ["public address", "volume", "sound", "loud"]}, "megaphone": {"a": "megaphone", "b": "1F4E3", "j": ["volume", "sound", "cheering", "speaker"]}, "postal-horn": {"a": "postal horn", "b": "1F4EF", "j": ["instrument", "horn", "post", "postal", "music"]}, "bell": {"a": "bell", "b": "1F514", "j": ["sound", "xmas", "chime", "notification", "christmas"]}, "bell-with-slash": {"a": "bell with slash", "b": "1F515", "j": ["forbidden", "sound", "mute", "bell", "quiet", "silent", "volume"]}, "musical-score": {"a": "musical score", "b": "1F3BC", "j": ["clef", "score", "treble", "compose", "music"]}, "musical-note": {"a": "musical note", "b": "1F3B5", "j": ["tone", "score", "sound", "note", "music"]}, "musical-notes": {"a": "musical notes", "b": "1F3B6", "j": ["notes", "note", "score", "music"]}, "studio-microphone": {"a": "studio microphone", "b": "1F399", "j": ["studio", "sing", "artist", "recording", "talkshow", "microphone", "mic", "music"]}, "level-slider": {"a": "level slider", "b": "1F39A", "j": ["slider", "music", "scale", "level"]}, "control-knobs": {"a": "control knobs", "b": "1F39B", "j": ["control", "dial", "music", "knobs"]}, "microphone": {"a": "microphone", "b": "1F3A4", "j": ["PA", "sing", "sound", "talkshow", "mic", "karaoke", "music"]}, "headphone": {"a": "headphone", "b": "1F3A7", "j": ["gadgets", "score", "earbud", "music"]}, "radio": {"a": "radio", "b": "1F4FB", "j": ["communication", "video", "program", "podcast", "music"]}, "saxophone": {"a": "saxophone", "b": "1F3B7", "j": ["sax", "instrument", "jazz", "blues", "music"]}, "accordion": {"a": "accordion", "b": "1FA97", "j": ["squeeze box", "music", "concertina"]}, "guitar": {"a": "guitar", "b": "1F3B8", "j": ["music", "instrument"]}, "musical-keyboard": {"a": "musical keyboard", "b": "1F3B9", "j": ["keyboard", "piano", "compose", "instrument", "music"]}, "trumpet": {"a": "trumpet", "b": "1F3BA", "j": ["brass", "music", "instrument"]}, "violin": {"a": "violin", "b": "1F3BB", "j": ["symphony", "orchestra", "music", "instrument"]}, "banjo": {"a": "banjo", "b": "1FA95", "j": ["instructment", "stringed", "music"]}, "drum": {"a": "drum", "b": "1F941", "j": ["drumsticks", "snare", "music", "instrument"]}, "long-drum": {"a": "long drum", "b": "1FA98", "j": ["drum", "beat", "conga", "music", "rhythm"]}, "mobile-phone": {"a": "mobile phone", "b": "1F4F1", "j": ["apple", "phone", "telephone", "mobile", "gadgets", "dial", "cell", "technology"]}, "mobile-phone-with-arrow": {"a": "mobile phone with arrow", "b": "1F4F2", "j": ["arrow", "incoming", "iphone", "phone", "mobile", "receive", "cell"]}, "telephone": {"a": "telephone", "b": "260E", "j": ["dial", "technology", "communication", "phone"]}, "telephone-receiver": {"a": "telephone receiver", "b": "1F4DE", "j": ["communication", "phone", "telephone", "dial", "receiver", "technology"]}, "pager": {"a": "pager", "b": "1F4DF", "j": ["90s", "bbcall", "oldschool"]}, "fax-machine": {"a": "fax machine", "b": "1F4E0", "j": ["fax", "technology", "communication"]}, "battery": {"a": "battery", "b": "1F50B", "j": ["energy", "power", "sustain"]}, "electric-plug": {"a": "electric plug", "b": "1F50C", "j": ["electric", "power", "charger", "electricity", "plug"]}, "laptop": {"a": "laptop", "b": "1F4BB", "j": ["computer", "display", "monitor", "pc", "screen", "technology", "personal"]}, "desktop-computer": {"a": "desktop computer", "b": "1F5A5", "j": ["computer", "desktop", "computing", "screen", "technology"]}, "printer": {"a": "printer", "b": "1F5A8", "j": ["ink", "computer", "paper"]}, "keyboard": {"a": "keyboard", "b": "2328", "j": ["computer", "text", "input", "type", "technology"]}, "computer-mouse": {"a": "computer mouse", "b": "1F5B1", "j": ["computer", "click"]}, "trackball": {"a": "trackball", "b": "1F5B2", "j": ["trackpad", "computer", "technology"]}, "computer-disk": {"a": "computer disk", "b": "1F4BD", "j": ["minidisk", "computer", "disk", "optical", "record", "90s", "technology", "data"]}, "floppy-disk": {"a": "floppy disk", "b": "1F4BE", "j": ["floppy", "computer", "disk", "80s", "save", "oldschool", "90s", "technology"]}, "optical-disk": {"a": "optical disk", "b": "1F4BF", "j": ["cd", "computer", "disk", "dvd", "optical", "90s", "technology", "disc"]}, "dvd": {"a": "dvd", "b": "1F4C0", "j": ["cd", "computer", "disk", "optical", "blu-ray", "disc"]}, "abacus": {"a": "abacus", "b": "1F9EE", "j": ["calculation"]}, "movie-camera": {"a": "movie camera", "b": "1F3A5", "j": ["cinema", "camera", "record", "film", "movie"]}, "film-frames": {"a": "film frames", "b": "1F39E", "j": ["frames", "movie", "cinema", "film"]}, "film-projector": {"a": "film projector", "b": "1F4FD", "j": ["cinema", "video", "record", "projector", "film", "tape", "movie"]}, "clapper-board": {"a": "clapper board", "b": "1F3AC", "j": ["movie", "clapper", "record", "film"]}, "television": {"a": "television", "b": "1F4FA", "j": ["video", "program", "oldschool", "show", "technology", "tv"]}, "camera": {"a": "camera", "b": "1F4F7", "j": ["gadgets", "photography", "video"]}, "camera-with-flash": {"a": "camera with flash", "b": "1F4F8", "j": ["video", "camera", "gadgets", "photography", "flash"]}, "video-camera": {"a": "video camera", "b": "1F4F9", "j": ["camera", "video", "record", "film"]}, "videocassette": {"a": "videocassette", "b": "1F4FC", "j": ["video", "80s", "record", "tape", "vhs", "oldschool", "90s"]}, "magnifying-glass-tilted-left": {"a": "magnifying glass tilted left", "b": "1F50D", "j": ["magnifying", "find", "search", "tool", "glass", "zoom", "detective"]}, "magnifying-glass-tilted-right": {"a": "magnifying glass tilted right", "b": "1F50E", "j": ["magnifying", "find", "search", "tool", "glass", "zoom", "detective"]}, "candle": {"a": "candle", "b": "1F56F", "j": ["wax", "light", "fire"]}, "light-bulb": {"a": "light bulb", "b": "1F4A1", "j": ["electric", "bulb", "comic", "light", "electricity", "idea"]}, "flashlight": {"a": "flashlight", "b": "1F526", "j": ["night", "electric", "sight", "torch", "light", "camping", "dark", "tool"]}, "red-paper-lantern": {"a": "red paper lantern", "b": "1F3EE", "j": ["paper", "bar", "halloween", "red", "light", "spooky", "lantern"]}, "diya-lamp": {"a": "diya lamp", "b": "1FA94", "j": ["lighting", "lamp", "diya", "oil"]}, "notebook-with-decorative-cover": {"a": "notebook with decorative cover", "b": "1F4D4", "j": ["book", "paper", "decorated", "record", "study", "cover", "classroom", "notes", "notebook"]}, "closed-book": {"a": "closed book", "b": "1F4D5", "j": ["learn", "book", "library", "read", "closed", "textbook", "knowledge"]}, "open-book": {"a": "open book", "b": "1F4D6", "j": ["learn", "book", "literature", "library", "study", "open", "read", "knowledge"]}, "green-book": {"a": "green book", "b": "1F4D7", "j": ["book", "library", "study", "read", "green", "knowledge"]}, "blue-book": {"a": "blue book", "b": "1F4D8", "j": ["blue", "learn", "book", "library", "study", "read", "knowledge"]}, "orange-book": {"a": "orange book", "b": "1F4D9", "j": ["book", "library", "orange", "study", "read", "textbook", "knowledge"]}, "books": {"a": "books", "b": "1F4DA", "j": ["book", "study", "library", "literature"]}, "notebook": {"a": "notebook", "b": "1F4D3", "j": ["paper", "record", "study", "stationery", "notes"]}, "ledger": {"a": "ledger", "b": "1F4D2", "j": ["notebook", "paper", "notes"]}, "page-with-curl": {"a": "page with curl", "b": "1F4C3", "j": ["document", "page", "curl", "paper", "documents", "office"]}, "scroll": {"a": "scroll", "b": "1F4DC", "j": ["paper", "history", "documents", "ancient"]}, "page-facing-up": {"a": "page facing up", "b": "1F4C4", "j": ["document", "page", "paper", "documents", "office", "information"]}, "newspaper": {"a": "newspaper", "b": "1F4F0", "j": ["headline", "paper", "press", "news"]}, "rolledup-newspaper": {"a": "rolled-up newspaper", "b": "1F5DE", "j": ["rolled_up_newspaper", "paper", "press", "news", "headline", "rolled", "newspaper"]}, "bookmark-tabs": {"a": "bookmark tabs", "b": "1F4D1", "j": ["order", "mark", "bookmark", "save", "favorite", "marker", "tidy", "tabs"]}, "bookmark": {"a": "bookmark", "b": "1F516", "j": ["save", "label", "favorite", "mark"]}, "label": {"a": "label", "b": "1F3F7", "j": ["tag", "sale"]}, "money-bag": {"a": "money bag", "b": "1F4B0", "j": ["moneybag", "dollar", "payment", "bag", "money", "coins", "sale"]}, "coin": {"a": "coin", "b": "1FA99", "j": ["metal", "currency", "silver", "treasure", "money", "gold"]}, "yen-banknote": {"a": "yen banknote", "b": "1F4B4", "j": ["bill", "currency", "japanese", "banknote", "dollar", "note", "yen", "money", "sales"]}, "dollar-banknote": {"a": "dollar banknote", "b": "1F4B5", "j": ["bill", "currency", "banknote", "dollar", "note", "money", "sales"]}, "euro-banknote": {"a": "euro banknote", "b": "1F4B6", "j": ["bill", "currency", "euro", "banknote", "dollar", "note", "money", "sales"]}, "pound-banknote": {"a": "pound banknote", "b": "1F4B7", "j": ["bill", "england", "currency", "british", "banknote", "uk", "note", "sterling", "sales", "money", "pound", "bills"]}, "money-with-wings": {"a": "money with wings", "b": "1F4B8", "j": ["bill", "banknote", "dollar", "payment", "money", "fly", "wings", "bills", "sale"]}, "credit-card": {"a": "credit card", "b": "1F4B3", "j": ["shopping", "bill", "card", "dollar", "credit", "payment", "money", "sales"]}, "receipt": {"a": "receipt", "b": "1F9FE", "j": ["bookkeeping", "accounting", "expenses", "evidence", "proof"]}, "chart-increasing-with-yen": {"a": "chart increasing with yen", "b": "1F4B9", "j": ["stats", "graph", "growth", "chart", "yen", "money", "presentation", "green-square"]}, "envelope": {"a": "envelope", "b": "2709", "j": ["letter", "communication", "email", "inbox", "postal"]}, "email": {"a": "e-mail", "b": "1F4E7", "j": ["e_mail", "letter", "communication", "mail", "inbox"]}, "incoming-envelope": {"a": "incoming envelope", "b": "1F4E8", "j": ["envelope", "incoming", "letter", "email", "receive", "e-mail", "inbox"]}, "envelope-with-arrow": {"a": "envelope with arrow", "b": "1F4E9", "j": ["arrow", "envelope", "communication", "email", "outgoing", "e-mail"]}, "outbox-tray": {"a": "outbox tray", "b": "1F4E4", "j": ["letter", "email", "outbox", "mail", "inbox", "box", "sent", "tray"]}, "inbox-tray": {"a": "inbox tray", "b": "1F4E5", "j": ["letter", "email", "documents", "mail", "receive", "inbox", "box", "tray"]}, "package": {"a": "package", "b": "1F4E6", "j": ["moving", "gift", "mail", "cardboard", "box", "parcel"]}, "closed-mailbox-with-raised-flag": {"a": "closed mailbox with raised flag", "b": "1F4EB", "j": ["postbox", "communication", "email", "mailbox", "closed", "mail", "inbox"]}, "closed-mailbox-with-lowered-flag": {"a": "closed mailbox with lowered flag", "b": "1F4EA", "j": ["postbox", "communication", "email", "lowered", "mailbox", "closed", "mail", "inbox"]}, "open-mailbox-with-raised-flag": {"a": "open mailbox with raised flag", "b": "1F4EC", "j": ["postbox", "communication", "email", "open", "mailbox", "mail", "inbox"]}, "open-mailbox-with-lowered-flag": {"a": "open mailbox with lowered flag", "b": "1F4ED", "j": ["postbox", "email", "lowered", "open", "mailbox", "mail", "inbox"]}, "postbox": {"a": "postbox", "b": "1F4EE", "j": ["envelope", "letter", "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", "study", "school", "stationery", "writing", "write"]}, "black-nib": {"a": "black nib", "b": "2712", "j": ["stationery", "writing", "write", "nib", "pen"]}, "fountain-pen": {"a": "fountain pen", "b": "1F58B", "j": ["fountain", "stationery", "writing", "write", "pen"]}, "pen": {"a": "pen", "b": "1F58A", "j": ["ballpoint", "write", "stationery", "writing"]}, "paintbrush": {"a": "paintbrush", "b": "1F58C", "j": ["painting", "drawing", "art", "creativity"]}, "crayon": {"a": "crayon", "b": "1F58D", "j": ["creativity", "drawing"]}, "memo": {"a": "memo", "b": "1F4DD", "j": ["test", "pencil", "quiz", "compose", "paper", "documents", "study", "stationery", "writing", "write", "legal", "exam"]}, "briefcase": {"a": "briefcase", "b": "1F4BC", "j": ["job", "work", "business", "law", "documents", "career", "legal"]}, "file-folder": {"a": "file folder", "b": "1F4C1", "j": ["file", "business", "folder", "documents", "office"]}, "open-file-folder": {"a": "open file folder", "b": "1F4C2", "j": ["file", "folder", "documents", "open", "load"]}, "card-index-dividers": {"a": "card index dividers", "b": "1F5C2", "j": ["organizing", "business", "card", "stationery", "dividers", "index"]}, "calendar": {"a": "calendar", "b": "1F4C5", "j": ["date", "schedule"]}, "tearoff-calendar": {"a": "tear-off calendar", "b": "1F4C6", "j": ["date", "schedule", "calendar", "planning", "tear_off_calendar"]}, "spiral-notepad": {"a": "spiral notepad", "b": "1F5D2", "j": ["pad", "memo", "stationery", "note", "spiral"]}, "spiral-calendar": {"a": "spiral calendar", "b": "1F5D3", "j": ["pad", "date", "calendar", "schedule", "planning", "spiral"]}, "card-index": {"a": "card index", "b": "1F4C7", "j": ["business", "card", "stationery", "rolodex", "index"]}, "chart-increasing": {"a": "chart increasing", "b": "1F4C8", "j": ["stats", "upward", "business", "graph", "growth", "good", "trend", "chart", "success", "recovery", "money", "economics", "presentation", "sales"]}, "chart-decreasing": {"a": "chart decreasing", "b": "1F4C9", "j": ["stats", "business", "failure", "recession", "graph", "down", "bad", "trend", "chart", "money", "economics", "presentation", "sales"]}, "bar-chart": {"a": "bar chart", "b": "1F4CA", "j": ["stats", "graph", "bar", "chart", "presentation"]}, "clipboard": {"a": "clipboard", "b": "1F4CB", "j": ["documents", "stationery"]}, "pushpin": {"a": "pushpin", "b": "1F4CC", "j": ["here", "pin", "stationery", "mark"]}, "round-pushpin": {"a": "round pushpin", "b": "1F4CD", "j": ["pin", "map", "pushpin", "location", "stationery", "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": ["architect", "math", "school", "stationery", "length", "drawing", "sketch", "calculate", "straight edge", "ruler"]}, "triangular-ruler": {"a": "triangular ruler", "b": "1F4D0", "j": ["architect", "math", "triangle", "stationery", "set", "sketch", "ruler"]}, "scissors": {"a": "scissors", "b": "2702", "j": ["cutting", "tool", "stationery", "cut"]}, "card-file-box": {"a": "card file box", "b": "1F5C3", "j": ["business", "file", "card", "stationery", "box"]}, "file-cabinet": {"a": "file cabinet", "b": "1F5C4", "j": ["file", "cabinet", "organizing", "filing"]}, "wastebasket": {"a": "wastebasket", "b": "1F5D1", "j": ["rubbish", "toss", "garbage", "trash", "bin"]}, "locked": {"a": "locked", "b": "1F512", "j": ["password", "padlock", "closed", "security"]}, "unlocked": {"a": "unlocked", "b": "1F513", "j": ["privacy", "open", "unlock", "lock", "security"]}, "locked-with-pen": {"a": "locked with pen", "b": "1F50F", "j": ["secret", "privacy", "lock", "nib", "security", "ink", "pen"]}, "locked-with-key": {"a": "locked with key", "b": "1F510", "j": ["privacy", "lock", "key", "closed", "security", "secure"]}, "key": {"a": "key", "b": "1F511", "j": ["password", "door", "lock"]}, "old-key": {"a": "old key", "b": "1F5DD", "j": ["door", "clue", "lock", "key", "old", "password"]}, "hammer": {"a": "hammer", "b": "1F528", "j": ["tool", "tools", "build", "create"]}, "axe": {"a": "axe", "b": "1FA93", "j": ["wood", "split", "chop", "tool", "hatchet", "cut"]}, "pick": {"a": "pick", "b": "26CF", "j": ["tool", "dig", "mining", "tools"]}, "hammer-and-pick": {"a": "hammer and pick", "b": "2692", "j": ["hammer", "build", "pick", "tools", "tool", "create"]}, "hammer-and-wrench": {"a": "hammer and wrench", "b": "1F6E0", "j": ["hammer", "build", "spanner", "tools", "tool", "wrench", "create"]}, "dagger": {"a": "dagger", "b": "1F5E1", "j": ["weapon", "knife"]}, "crossed-swords": {"a": "crossed swords", "b": "2694", "j": ["weapon", "crossed", "swords"]}, "water-pistol": {"a": "water pistol", "b": "1F52B", "j": ["violence", "tool", "water", "revolver", "weapon", "gun", "pistol", "handgun"]}, "boomerang": {"a": "boomerang", "b": "1FA83", "j": ["rebound", "australia", "repercussion", "weapon"]}, "bow-and-arrow": {"a": "bow and arrow", "b": "1F3F9", "j": ["arrow", "archer", "zodiac", "sports", "Sagittarius", "bow"]}, "shield": {"a": "shield", "b": "1F6E1", "j": ["weapon", "protection", "security"]}, "carpentry-saw": {"a": "carpentry saw", "b": "1FA9A", "j": ["carpenter", "lumber", "saw", "chop", "tool", "cut"]}, "wrench": {"a": "wrench", "b": "1F527", "j": ["diy", "ikea", "spanner", "tools", "fix", "maintainer", "tool"]}, "screwdriver": {"a": "screwdriver", "b": "1FA9B", "j": ["tool", "screw", "tools"]}, "nut-and-bolt": {"a": "nut and bolt", "b": "1F529", "j": ["handy", "tools", "fix", "nut", "tool", "bolt"]}, "gear": {"a": "gear", "b": "2699", "j": ["cog", "cogwheel", "tool"]}, "clamp": {"a": "clamp", "b": "1F5DC", "j": ["vice", "tool", "compress"]}, "balance-scale": {"a": "balance scale", "b": "2696", "j": ["balance", "Libra", "law", "zodiac", "fairness", "justice", "scale", "weight"]}, "white-cane": {"a": "white cane", "b": "1F9AF", "j": ["blind", "probing_cane", "accessibility"]}, "link": {"a": "link", "b": "1F517", "j": ["url", "rings"]}, "chains": {"a": "chains", "b": "26D3", "j": ["arrest", "lock", "chain"]}, "hook": {"a": "hook", "b": "1FA9D", "j": ["crook", "curve", "catch", "selling point", "tools", "ensnare"]}, "toolbox": {"a": "toolbox", "b": "1F9F0", "j": ["chest", "diy", "tools", "fix", "maintainer", "mechanic", "tool"]}, "magnet": {"a": "magnet", "b": "1F9F2", "j": ["attraction", "magnetic", "horseshoe"]}, "ladder": {"a": "ladder", "b": "1FA9C", "j": ["rung", "step", "tools", "climb"]}, "alembic": {"a": "alembic", "b": "2697", "j": ["chemistry", "distilling", "experiment", "science", "tool"]}, "test-tube": {"a": "test tube", "b": "1F9EA", "j": ["chemistry", "experiment", "lab", "science", "chemist"]}, "petri-dish": {"a": "petri dish", "b": "1F9EB", "j": ["biologist", "culture", "biology", "lab", "bacteria"]}, "dna": {"a": "dna", "b": "1F9EC", "j": ["biologist", "life", "gene", "evolution", "genetics"]}, "microscope": {"a": "microscope", "b": "1F52C", "j": ["study", "experiment", "laboratory", "science", "tool", "zoomin"]}, "telescope": {"a": "telescope", "b": "1F52D", "j": ["stars", "astronomy", "space", "science", "tool", "zoom"]}, "satellite-antenna": {"a": "satellite antenna", "b": "1F4E1", "j": ["communication", "satellite", "space", "antenna", "dish", "future", "radio"]}, "syringe": {"a": "syringe", "b": "1F489", "j": ["nurse", "shot", "doctor", "medicine", "hospital", "health", "sick", "needle", "drugs", "blood"]}, "drop-of-blood": {"a": "drop of blood", "b": "1FA78", "j": ["bleed", "menstruation", "harm", "medicine", "wound", "period", "blood donation", "injury", "hurt"]}, "pill": {"a": "pill", "b": "1F48A", "j": ["drug", "doctor", "medicine", "health", "sick", "pharmacy"]}, "adhesive-bandage": {"a": "adhesive bandage", "b": "1FA79", "j": ["heal", "bandage"]}, "stethoscope": {"a": "stethoscope", "b": "1FA7A", "j": ["heart", "medicine", "doctor", "health"]}, "door": {"a": "door", "b": "1F6AA", "j": ["house", "entry", "exit"]}, "elevator": {"a": "elevator", "b": "1F6D7", "j": ["lift", "hoist", "accessibility"]}, "mirror": {"a": "mirror", "b": "1FA9E", "j": ["reflection", "reflector", "speculum"]}, "window": {"a": "window", "b": "1FA9F", "j": ["transparent", "fresh air", "opening", "view", "scenery", "frame"]}, "bed": {"a": "bed", "b": "1F6CF", "j": ["sleep", "hotel", "rest"]}, "couch-and-lamp": {"a": "couch and lamp", "b": "1F6CB", "j": ["lamp", "chill", "couch", "read", "hotel"]}, "chair": {"a": "chair", "b": "1FA91", "j": ["sit", "furniture", "seat"]}, "toilet": {"a": "toilet", "b": "1F6BD", "j": ["potty", "bathroom", "washroom", "restroom", "wc"]}, "plunger": {"a": "plunger", "b": "1FAA0", "j": ["suction", "plumber", "force cup", "toilet"]}, "shower": {"a": "shower", "b": "1F6BF", "j": ["bathroom", "water", "clean"]}, "bathtub": {"a": "bathtub", "b": "1F6C1", "j": ["shower", "bathroom", "bath", "clean"]}, "mouse-trap": {"a": "mouse trap", "b": "1FAA4", "j": ["trap", "cheese", "bait", "mousetrap", "snare"]}, "razor": {"a": "razor", "b": "1FA92", "j": ["shave", "sharp", "cut"]}, "lotion-bottle": {"a": "lotion bottle", "b": "1F9F4", "j": ["sunscreen", "shampoo", "lotion", "moisturizer"]}, "safety-pin": {"a": "safety pin", "b": "1F9F7", "j": ["punk rock", "diaper"]}, "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": ["toilet paper", "roll", "paper towels"]}, "bucket": {"a": "bucket", "b": "1FAA3", "j": ["cask", "water", "pail", "vat", "container"]}, "soap": {"a": "soap", "b": "1F9FC", "j": ["soapdish", "bar", "lather", "bathing", "cleaning"]}, "toothbrush": {"a": "toothbrush", "b": "1FAA5", "j": ["brush", "bathroom", "hygiene", "teeth", "clean", "dental"]}, "sponge": {"a": "sponge", "b": "1F9FD", "j": ["absorbing", "cleaning", "porous"]}, "fire-extinguisher": {"a": "fire extinguisher", "b": "1F9EF", "j": ["quench", "extinguish", "fire"]}, "shopping-cart": {"a": "shopping cart", "b": "1F6D2", "j": ["shopping", "cart", "trolley"]}, "cigarette": {"a": "cigarette", "b": "1F6AC", "j": ["smoke", "smoking", "kills", "tobacco", "joint"]}, "coffin": {"a": "coffin", "b": "26B0", "j": ["death", "vampire", "rip", "casket", "funeral", "die", "dead", "box", "cemetery", "graveyard"]}, "headstone": {"a": "headstone", "b": "1FAA6", "j": ["death", "rip", "tombstone", "grave", "cemetery", "graveyard"]}, "funeral-urn": {"a": "funeral urn", "b": "26B1", "j": ["death", "rip", "funeral", "ashes", "die", "dead", "urn"]}, "moai": {"a": "moai", "b": "1F5FF", "j": ["rock", "moyai", "face", "statue", "easter island"]}, "placard": {"a": "placard", "b": "1FAA7", "j": ["sign", "announcement", "picket", "protest", "demonstration"]}, "atm-sign": {"a": "ATM sign", "b": "1F3E7", "j": ["cash", "bank", "teller", "automated", "atm", "blue-square", "payment", "money", "sales"]}, "litter-in-bin-sign": {"a": "litter in bin sign", "b": "1F6AE", "j": ["info", "sign", "litter", "human", "blue-square", "litter bin"]}, "potable-water": {"a": "potable water", "b": "1F6B0", "j": ["restroom", "water", "drinking", "blue-square", "liquid", "potable", "faucet", "cleaning"]}, "wheelchair-symbol": {"a": "wheelchair symbol", "b": "267F", "j": ["blue-square", "access", "disabled", "accessibility"]}, "mens-room": {"a": "men’s room", "b": "1F6B9", "j": ["gender", "man", "male", "restroom", "blue-square", "lavatory", "toilet", "men_s_room", "wc"]}, "womens-room": {"a": "women’s room", "b": "1F6BA", "j": ["gender", "woman", "restroom", "purple-square", "loo", "lavatory", "women_s_room", "toilet", "wc", "female"]}, "restroom": {"a": "restroom", "b": "1F6BB", "j": ["gender", "WC", "blue-square", "lavatory", "toilet", "refresh", "wc"]}, "baby-symbol": {"a": "baby symbol", "b": "1F6BC", "j": ["changing", "orange-square", "baby", "child"]}, "water-closet": {"a": "water closet", "b": "1F6BE", "j": ["restroom", "water", "blue-square", "lavatory", "closet", "toilet", "wc"]}, "passport-control": {"a": "passport control", "b": "1F6C2", "j": ["custom", "blue-square", "control", "passport"]}, "customs": {"a": "customs", "b": "1F6C3", "j": ["blue-square", "border", "passport"]}, "baggage-claim": {"a": "baggage claim", "b": "1F6C4", "j": ["transport", "claim", "baggage", "blue-square", "airport"]}, "left-luggage": {"a": "left luggage", "b": "1F6C5", "j": ["locker", "luggage", "baggage", "blue-square", "travel"]}, "warning": {"a": "warning", "b": "26A0", "j": ["alert", "wip", "problem", "error", "issue", "exclamation"]}, "children-crossing": {"a": "children crossing", "b": "1F6B8", "j": ["danger", "warning", "sign", "crossing", "yellow-diamond", "traffic", "pedestrian", "school", "child", "driving"]}, "no-entry": {"a": "no entry", "b": "26D4", "j": ["forbidden", "circle", "denied", "privacy", "stop", "no", "entry", "prohibited", "traffic", "bad", "not", "limit", "security"]}, "prohibited": {"a": "prohibited", "b": "1F6AB", "j": ["forbidden", "circle", "denied", "stop", "no", "entry", "disallow", "not", "limit", "forbid"]}, "no-bicycles": {"a": "no bicycles", "b": "1F6B3", "j": ["forbidden", "circle", "no", "prohibited", "bicycle", "bike", "cyclist"]}, "no-smoking": {"a": "no smoking", "b": "1F6AD", "j": ["smoke", "forbidden", "smoking", "smell", "no", "prohibited", "cigarette", "blue-square", "not"]}, "no-littering": {"a": "no littering", "b": "1F6AF", "j": ["forbidden", "circle", "litter", "no", "prohibited", "garbage", "not", "trash", "bin"]}, "nonpotable-water": {"a": "non-potable water", "b": "1F6B1", "j": ["non_potable_water", "circle", "non-drinking", "drink", "water", "faucet", "tap", "non-potable"]}, "no-pedestrians": {"a": "no pedestrians", "b": "1F6B7", "j": ["forbidden", "circle", "crossing", "no", "prohibited", "pedestrian", "walking", "not", "rules"]}, "no-mobile-phones": {"a": "no mobile phones", "b": "1F4F5", "j": ["forbidden", "circle", "iphone", "phone", "no", "mute", "mobile", "cell"]}, "no-one-under-eighteen": {"a": "no one under eighteen", "b": "1F51E", "j": ["pub", "underage", "night", "circle", "prohibited", "drink", "18", "eighteen", "age restriction", "minor"]}, "radioactive": {"a": "radioactive", "b": "2622", "j": ["danger", "sign", "nuclear"]}, "biohazard": {"a": "biohazard", "b": "2623", "j": ["danger", "sign"]}, "up-arrow": {"a": "up arrow", "b": "2B06", "j": ["arrow", "top", "blue-square", "continue", "direction", "north", "cardinal"]}, "upright-arrow": {"a": "up-right arrow", "b": "2197", "j": ["arrow", "up_right_arrow", "point", "diagonal", "blue-square", "direction", "northeast", "intercardinal"]}, "right-arrow": {"a": "right arrow", "b": "27A1", "j": ["arrow", "east", "blue-square", "direction", "next", "cardinal"]}, "downright-arrow": {"a": "down-right arrow", "b": "2198", "j": ["arrow", "diagonal", "blue-square", "down_right_arrow", "direction", "intercardinal", "southeast"]}, "down-arrow": {"a": "down arrow", "b": "2B07", "j": ["arrow", "down", "blue-square", "cardinal", "direction", "bottom", "south"]}, "downleft-arrow": {"a": "down-left arrow", "b": "2199", "j": ["arrow", "southwest", "diagonal", "blue-square", "down_left_arrow", "direction", "intercardinal"]}, "left-arrow": {"a": "left arrow", "b": "2B05", "j": ["arrow", "west", "blue-square", "previous", "back", "direction", "cardinal"]}, "upleft-arrow": {"a": "up-left arrow", "b": "2196", "j": ["arrow", "point", "northwest", "up_left_arrow", "diagonal", "blue-square", "direction", "intercardinal"]}, "updown-arrow": {"a": "up-down arrow", "b": "2195", "j": ["arrow", "blue-square", "vertical", "direction", "up_down_arrow", "way"]}, "leftright-arrow": {"a": "left-right arrow", "b": "2194", "j": ["left_right_arrow", "arrow", "sideways", "shape", "direction", "horizontal"]}, "right-arrow-curving-left": {"a": "right arrow curving left", "b": "21A9", "j": ["arrow", "undo", "back", "return", "blue-square", "enter"]}, "left-arrow-curving-right": {"a": "left arrow curving right", "b": "21AA", "j": ["arrow", "blue-square", "return", "direction", "rotate"]}, "right-arrow-curving-up": {"a": "right arrow curving up", "b": "2934", "j": ["blue-square", "arrow", "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", "sync", "round", "cycle", "repeat", "reload", "clockwise"]}, "counterclockwise-arrows-button": {"a": "counterclockwise arrows button", "b": "1F504", "j": ["arrow", "sync", "blue-square", "cycle", "anticlockwise", "withershins", "counterclockwise"]}, "back-arrow": {"a": "BACK arrow", "b": "1F519", "j": ["back", "arrow", "return", "words"]}, "end-arrow": {"a": "END arrow", "b": "1F51A", "j": ["end", "arrow", "words"]}, "on-arrow": {"a": "ON! arrow", "b": "1F51B", "j": ["arrow", "words", "on", "mark"]}, "soon-arrow": {"a": "SOON arrow", "b": "1F51C", "j": ["arrow", "soon", "words"]}, "top-arrow": {"a": "TOP arrow", "b": "1F51D", "j": ["arrow", "top", "words", "blue-square", "up"]}, "place-of-worship": {"a": "place of worship", "b": "1F6D0", "j": ["church", "worship", "prayer", "temple", "religion"]}, "atom-symbol": {"a": "atom symbol", "b": "269B", "j": ["chemistry", "atom", "atheist", "science", "physics"]}, "om": {"a": "om", "b": "1F549", "j": ["sikhism", "Hindu", "hinduism", "buddhism", "jainism", "religion"]}, "star-of-david": {"a": "star of David", "b": "2721", "j": ["star", "Jew", "David", "Jewish", "judaism", "religion"]}, "wheel-of-dharma": {"a": "wheel of dharma", "b": "2638", "j": ["dharma", "sikhism", "wheel", "hinduism", "buddhism", "jainism", "Buddhist", "religion"]}, "yin-yang": {"a": "yin yang", "b": "262F", "j": ["balance", "yang", "tao", "taoist", "yin", "religion"]}, "latin-cross": {"a": "latin cross", "b": "271D", "j": ["religion", "cross", "christianity", "Christian"]}, "orthodox-cross": {"a": "orthodox cross", "b": "2626", "j": ["cross", "religion", "suppedaneum", "Christian"]}, "star-and-crescent": {"a": "star and crescent", "b": "262A", "j": ["islam", "Muslim", "religion"]}, "peace-symbol": {"a": "peace symbol", "b": "262E", "j": ["hippie", "peace"]}, "menorah": {"a": "menorah", "b": "1F54E", "j": ["candlestick", "hanukkah", "jewish", "candelabrum", "candles", "religion"]}, "dotted-sixpointed-star": {"a": "dotted six-pointed star", "b": "1F52F", "j": ["jewish", "star", "purple-square", "dotted_six_pointed_star", "fortune", "religion", "hexagram"]}, "aries": {"a": "Aries", "b": "2648", "j": ["astrology", "sign", "ram", "purple-square", "zodiac"]}, "taurus": {"a": "Taurus", "b": "2649", "j": ["astrology", "sign", "purple-square", "ox", "zodiac", "bull"]}, "gemini": {"a": "Gemini", "b": "264A", "j": ["astrology", "sign", "twins", "purple-square", "zodiac"]}, "cancer": {"a": "Cancer", "b": "264B", "j": ["astrology", "sign", "purple-square", "zodiac", "crab"]}, "leo": {"a": "Leo", "b": "264C", "j": ["astrology", "sign", "lion", "purple-square", "zodiac"]}, "virgo": {"a": "Virgo", "b": "264D", "j": ["astrology", "sign", "purple-square", "zodiac"]}, "libra": {"a": "Libra", "b": "264E", "j": ["astrology", "sign", "balance", "purple-square", "zodiac", "scales", "justice"]}, "scorpio": {"a": "Scorpio", "b": "264F", "j": ["astrology", "sign", "scorpion", "purple-square", "zodiac", "scorpius"]}, "sagittarius": {"a": "Sagittarius", "b": "2650", "j": ["astrology", "sign", "archer", "purple-square", "zodiac"]}, "capricorn": {"a": "Capricorn", "b": "2651", "j": ["astrology", "sign", "purple-square", "zodiac", "goat"]}, "aquarius": {"a": "Aquarius", "b": "2652", "j": ["astrology", "sign", "purple-square", "water", "zodiac", "bearer"]}, "pisces": {"a": "Pisces", "b": "2653", "j": ["astrology", "sign", "purple-square", "fish", "zodiac"]}, "ophiuchus": {"a": "Ophiuchus", "b": "26CE", "j": ["astrology", "sign", "serpent", "snake", "purple-square", "zodiac", "constellation", "bearer"]}, "shuffle-tracks-button": {"a": "shuffle tracks button", "b": "1F500", "j": ["arrow", "random", "crossed", "blue-square", "shuffle", "music"]}, "repeat-button": {"a": "repeat button", "b": "1F501", "j": ["arrow", "record", "loop", "repeat", "clockwise"]}, "repeat-single-button": {"a": "repeat single button", "b": "1F502", "j": ["arrow", "once", "blue-square", "loop", "clockwise"]}, "play-button": {"a": "play button", "b": "25B6", "j": ["arrow", "play", "triangle", "blue-square", "direction", "right"]}, "fastforward-button": {"a": "fast-forward button", "b": "23E9", "j": ["arrow", "play", "fast", "fast_forward_button", "blue-square", "speed", "continue", "double", "forward"]}, "next-track-button": {"a": "next track button", "b": "23ED", "j": ["arrow", "next track", "next scene", "triangle", "blue-square", "next", "forward"]}, "play-or-pause-button": {"a": "play or pause button", "b": "23EF", "j": ["arrow", "play", "triangle", "blue-square", "pause", "right"]}, "reverse-button": {"a": "reverse button", "b": "25C0", "j": ["arrow", "reverse", "triangle", "left", "blue-square", "direction"]}, "fast-reverse-button": {"a": "fast reverse button", "b": "23EA", "j": ["arrow", "play", "blue-square", "double", "rewind"]}, "last-track-button": {"a": "last track button", "b": "23EE", "j": ["previous track", "arrow", "previous scene", "triangle", "backward"]}, "upwards-button": {"a": "upwards button", "b": "1F53C", "j": ["arrow", "top", "point", "triangle", "blue-square", "red", "direction", "button", "forward"]}, "fast-up-button": {"a": "fast up button", "b": "23EB", "j": ["arrow", "top", "blue-square", "direction", "double"]}, "downwards-button": {"a": "downwards button", "b": "1F53D", "j": ["arrow", "down", "blue-square", "red", "direction", "button", "bottom"]}, "fast-down-button": {"a": "fast down button", "b": "23EC", "j": ["arrow", "down", "blue-square", "direction", "double", "bottom"]}, "pause-button": {"a": "pause button", "b": "23F8", "j": ["bar", "blue-square", "vertical", "double", "pause"]}, "stop-button": {"a": "stop button", "b": "23F9", "j": ["blue-square", "square", "stop"]}, "record-button": {"a": "record button", "b": "23FA", "j": ["blue-square", "circle", "record"]}, "eject-button": {"a": "eject button", "b": "23CF", "j": ["blue-square", "eject"]}, "cinema": {"a": "cinema", "b": "1F3A6", "j": ["theater", "stage", "camera", "record", "curtain", "film", "blue-square", "movie"]}, "dim-button": {"a": "dim button", "b": "1F505", "j": ["sun", "brightness", "low", "warm", "afternoon", "dim", "summer"]}, "bright-button": {"a": "bright button", "b": "1F506", "j": ["sun", "brightness", "light", "bright"]}, "antenna-bars": {"a": "antenna bars", "b": "1F4F6", "j": ["phone", "bars", "bluetooth", "bar", "mobile", "blue-square", "internet", "antenna", "connection", "wifi", "cell", "reception"]}, "vibration-mode": {"a": "vibration mode", "b": "1F4F3", "j": ["phone", "orange-square", "telephone", "mobile", "vibration", "mode", "cell"]}, "mobile-phone-off": {"a": "mobile phone off", "b": "1F4F4", "j": ["silence", "phone", "orange-square", "telephone", "mute", "mobile", "quiet", "cell", "off"]}, "female-sign": {"a": "female sign", "b": "2640", "j": ["lady", "women", "girl", "woman"]}, "male-sign": {"a": "male sign", "b": "2642", "j": ["boy", "man", "men"]}, "transgender-symbol": {"a": "transgender symbol", "b": "26A7", "j": ["transgender", "lgbtq"]}, "multiply": {"a": "multiply", "b": "2716", "j": ["sign", "multiplication_sign", "math", "cancel", "multiplication", "calculation", "x", "×"]}, "plus": {"a": "plus", "b": "2795", "j": ["sign", "+", "increase", "math", "more", "calculation", "plus_sign", "addition"]}, "minus": {"a": "minus", "b": "2796", "j": ["minus_sign", "sign", "math", "−", "calculation", "-", "less", "subtract"]}, "divide": {"a": "divide", "b": "2797", "j": ["sign", "division_sign", "math", "÷", "division", "calculation"]}, "infinity": {"a": "infinity", "b": "267E", "j": ["unbounded", "universal", "forever"]}, "double-exclamation-mark": {"a": "double exclamation mark", "b": "203C", "j": ["!!", "mark", "bangbang", "exclamation", "!", "surprise"]}, "exclamation-question-mark": {"a": "exclamation question mark", "b": "2049", "j": ["punctuation", "interrobang", "mark", "exclamation", "?", "wat", "!?", "question", "!", "surprise"]}, "red-question-mark": {"a": "red question mark", "b": "2753", "j": ["punctuation", "question_mark", "mark", "?", "question", "confused", "doubt"]}, "white-question-mark": {"a": "white question mark", "b": "2754", "j": ["confused", "punctuation", "mark", "?", "outlined", "huh", "doubts", "question", "gray"]}, "white-exclamation-mark": {"a": "white exclamation mark", "b": "2755", "j": ["warning", "punctuation", "mark", "wow", "outlined", "exclamation", "!", "surprise", "gray"]}, "red-exclamation-mark": {"a": "red exclamation mark", "b": "2757", "j": ["danger", "warning", "punctuation", "heavy_exclamation_mark", "mark", "exclamation_mark", "wow", "exclamation", "!", "surprise"]}, "wavy-dash": {"a": "wavy dash", "b": "3030", "j": ["moustache", "punctuation", "draw", "dash", "squiggle", "scribble", "mustache", "line", "wavy"]}, "currency-exchange": {"a": "currency exchange", "b": "1F4B1", "j": ["currency", "bank", "dollar", "travel", "exchange", "money", "sales"]}, "heavy-dollar-sign": {"a": "heavy dollar sign", "b": "1F4B2", "j": ["currency", "dollar", "payment", "money", "sales", "buck"]}, "medical-symbol": {"a": "medical symbol", "b": "2695", "j": ["staff", "aesculapius", "hospital", "medicine", "health"]}, "recycling-symbol": {"a": "recycling symbol", "b": "267B", "j": ["arrow", "recycle", "environment", "garbage", "trash"]}, "fleurdelis": {"a": "fleur-de-lis", "b": "269C", "j": ["fleur_de_lis", "decorative", "scout"]}, "trident-emblem": {"a": "trident emblem", "b": "1F531", "j": ["emblem", "anchor", "weapon", "ship", "tool", "spear", "trident"]}, "name-badge": {"a": "name badge", "b": "1F4DB", "j": ["name", "forbid", "fire", "badge"]}, "japanese-symbol-for-beginner": {"a": "Japanese symbol for beginner", "b": "1F530", "j": ["chevron", "badge", "leaf", "shield", "Japanese", "beginner"]}, "hollow-red-circle": {"a": "hollow red circle", "b": "2B55", "j": ["large", "circle", "o", "red", "round"]}, "check-mark-button": {"a": "check mark button", "b": "2705", "j": ["election", "check", "✓", "green-square", "mark", "vote", "agree", "answer", "tick", "button", "ok"]}, "check-box-with-check": {"a": "check box with check", "b": "2611", "j": ["election", "yes", "check", "✓", "black-square", "vote", "confirm", "agree", "tick", "box", "ok"]}, "check-mark": {"a": "check mark", "b": "2714", "j": ["yes", "check", "✓", "nike", "mark", "answer", "tick", "ok"]}, "cross-mark": {"a": "cross mark", "b": "274C", "j": ["cross", "mark", "no", "delete", "cancel", "multiply", "remove", "multiplication", "red", "x", "×"]}, "cross-mark-button": {"a": "cross mark button", "b": "274E", "j": ["mark", "no", "square", "deny", "x", "green-square", "×"]}, "curly-loop": {"a": "curly loop", "b": "27B0", "j": ["draw", "curl", "shape", "squiggle", "scribble", "loop"]}, "double-curly-loop": {"a": "double curly loop", "b": "27BF", "j": ["curl", "tape", "cassette", "double", "loop"]}, "part-alternation-mark": {"a": "part alternation mark", "b": "303D", "j": ["stats", "business", "mark", "graph", "bad", "part", "economics", "presentation"]}, "eightspoked-asterisk": {"a": "eight-spoked asterisk", "b": "2733", "j": ["eight_spoked_asterisk", "star", "*", "sparkle", "asterisk", "green-square"]}, "eightpointed-star": {"a": "eight-pointed star", "b": "2734", "j": ["polygon", "orange-square", "star", "*", "shape", "eight_pointed_star"]}, "sparkle": {"a": "sparkle", "b": "2747", "j": ["awesome", "fireworks", "good", "stars", "*", "green-square"]}, "copyright": {"a": "copyright", "b": "00A9", "j": ["ip", "circle", "license", "law", "legal", "c"]}, "registered": {"a": "registered", "b": "00AE", "j": ["alphabet", "circle", "r"]}, "trade-mark": {"a": "trade mark", "b": "2122", "j": ["mark", "tm", "law", "brand", "legal", "trademark"]}, "keycap": {"a": "keycap: *", "b": "002A-FE0F-20E3", "j": ["star", "keycap_"]}, "keycap-0": {"a": "keycap: 0", "b": "0030-FE0F-20E3", "j": ["0", "keycap", "numbers", "blue-square", "null"]}, "keycap-1": {"a": "keycap: 1", "b": "0031-FE0F-20E3", "j": ["numbers", "blue-square", "1", "keycap"]}, "keycap-2": {"a": "keycap: 2", "b": "0032-FE0F-20E3", "j": ["keycap", "numbers", "2", "blue-square", "prime"]}, "keycap-3": {"a": "keycap: 3", "b": "0033-FE0F-20E3", "j": ["keycap", "numbers", "3", "blue-square", "prime"]}, "keycap-4": {"a": "keycap: 4", "b": "0034-FE0F-20E3", "j": ["numbers", "blue-square", "4", "keycap"]}, "keycap-5": {"a": "keycap: 5", "b": "0035-FE0F-20E3", "j": ["keycap", "numbers", "blue-square", "prime", "5"]}, "keycap-6": {"a": "keycap: 6", "b": "0036-FE0F-20E3", "j": ["numbers", "6", "keycap", "blue-square"]}, "keycap-7": {"a": "keycap: 7", "b": "0037-FE0F-20E3", "j": ["7", "keycap", "numbers", "blue-square", "prime"]}, "keycap-8": {"a": "keycap: 8", "b": "0038-FE0F-20E3", "j": ["numbers", "blue-square", "8", "keycap"]}, "keycap-9": {"a": "keycap: 9", "b": "0039-FE0F-20E3", "j": ["numbers", "blue-square", "9", "keycap"]}, "keycap-10": {"a": "keycap: 10", "b": "1F51F", "j": ["numbers", "blue-square", "10", "keycap"]}, "input-latin-uppercase": {"a": "input latin uppercase", "b": "1F520", "j": ["latin", "words", "alphabet", "input", "ABCD", "uppercase", "blue-square", "letters"]}, "input-latin-lowercase": {"a": "input latin lowercase", "b": "1F521", "j": ["latin", "alphabet", "abcd", "input", "blue-square", "letters", "lowercase"]}, "input-numbers": {"a": "input numbers", "b": "1F522", "j": ["numbers", "blue-square", "1234", "input"]}, "input-symbols": {"a": "input symbols", "b": "1F523", "j": ["percent", "〒♪&%", "ampersand", "input", "blue-square", "note", "glyphs", "characters", "music"]}, "input-latin-letters": {"a": "input latin letters", "b": "1F524", "j": ["latin", "alphabet", "input", "blue-square", "abc", "letters"]}, "a-button-blood-type": {"a": "A button (blood type)", "b": "1F170", "j": ["a_button", "letter", "alphabet", "a", "red-square", "blood type"]}, "ab-button-blood-type": {"a": "AB button (blood type)", "b": "1F18E", "j": ["ab_button", "alphabet", "red-square", "ab", "blood type"]}, "b-button-blood-type": {"a": "B button (blood type)", "b": "1F171", "j": ["letter", "alphabet", "b", "red-square", "b_button", "blood type"]}, "cl-button": {"a": "CL button", "b": "1F191", "j": ["alphabet", "red-square", "words", "cl"]}, "cool-button": {"a": "COOL button", "b": "1F192", "j": ["blue-square", "words", "cool"]}, "free-button": {"a": "FREE button", "b": "1F193", "j": ["blue-square", "free", "words"]}, "information": {"a": "information", "b": "2139", "j": ["i", "alphabet", "blue-square", "letter"]}, "id-button": {"a": "ID button", "b": "1F194", "j": ["id", "identity", "purple-square", "words"]}, "circled-m": {"a": "circled M", "b": "24C2", "j": ["circle", "blue-circle", "letter", "alphabet", "m"]}, "new-button": {"a": "NEW button", "b": "1F195", "j": ["blue-square", "start", "new", "words"]}, "ng-button": {"a": "NG button", "b": "1F196", "j": ["icon", "words", "blue-square", "ng", "shape"]}, "o-button-blood-type": {"a": "O button (blood type)", "b": "1F17E", "j": ["letter", "alphabet", "o", "o_button", "red-square", "blood type"]}, "ok-button": {"a": "OK button", "b": "1F197", "j": ["yes", "OK", "good", "agree", "blue-square"]}, "p-button": {"a": "P button", "b": "1F17F", "j": ["letter", "alphabet", "blue-square", "parking", "cars"]}, "sos-button": {"a": "SOS button", "b": "1F198", "j": ["words", "sos", "emergency", "red-square", "911", "help"]}, "up-button": {"a": "UP! button", "b": "1F199", "j": ["mark", "high", "above", "blue-square", "up"]}, "vs-button": {"a": "VS button", "b": "1F19A", "j": ["orange-square", "vs", "words", "versus"]}, "japanese-here-button": {"a": "Japanese “here” button", "b": "1F201", "j": ["destination", "ココ", "katakana", "Japanese", "japanese", "“here”", "blue-square", "here"]}, "japanese-service-charge-button": {"a": "Japanese “service charge” button", "b": "1F202", "j": ["katakana", "Japanese", "japanese", "blue-square", "“service charge”", "サ"]}, "japanese-monthly-amount-button": {"a": "Japanese “monthly amount” button", "b": "1F237", "j": ["orange-square", "moon", "Japanese", "japanese", "kanji", "ideograph", "月", "chinese", "“monthly amount”", "month"]}, "japanese-not-free-of-charge-button": {"a": "Japanese “not free of charge” button", "b": "1F236", "j": ["有", "orange-square", "Japanese", "have", "kanji", "ideograph", "chinese", "“not free of charge”"]}, "japanese-reserved-button": {"a": "Japanese “reserved” button", "b": "1F22F", "j": ["point", "“reserved”", "Japanese", "kanji", "ideograph", "chinese", "green-square", "指"]}, "japanese-bargain-button": {"a": "Japanese “bargain” button", "b": "1F250", "j": ["circle", "得", "obtain", "Japanese", "get", "“bargain”", "kanji", "ideograph", "chinese"]}, "japanese-discount-button": {"a": "Japanese “discount” button", "b": "1F239", "j": ["pink-square", "Japanese", "“discount”", "kanji", "ideograph", "割", "chinese", "divide", "cut"]}, "japanese-free-of-charge-button": {"a": "Japanese “free of charge” button", "b": "1F21A", "j": ["“free of charge”", "orange-square", "Japanese", "japanese", "nothing", "kanji", "ideograph", "chinese", "無"]}, "japanese-prohibited-button": {"a": "Japanese “prohibited” button", "b": "1F232", "j": ["“prohibited”", "forbidden", "禁", "Japanese", "japanese", "red-square", "kanji", "ideograph", "limit", "chinese", "restricted"]}, "japanese-acceptable-button": {"a": "Japanese “acceptable” button", "b": "1F251", "j": ["yes", "good", "orange-circle", "可", "Japanese", "agree", "kanji", "ideograph", "chinese", "ok", "“acceptable”"]}, "japanese-application-button": {"a": "Japanese “application” button", "b": "1F238", "j": ["orange-square", "Japanese", "japanese", "kanji", "ideograph", "“application”", "chinese", "申"]}, "japanese-passing-grade-button": {"a": "Japanese “passing grade” button", "b": "1F234", "j": ["join", "Japanese", "japanese", "“passing grade”", "red-square", "kanji", "ideograph", "chinese", "合"]}, "japanese-vacancy-button": {"a": "Japanese “vacancy” button", "b": "1F233", "j": ["“vacancy”", "空", "Japanese", "japanese", "blue-square", "sky", "kanji", "ideograph", "empty", "chinese"]}, "japanese-congratulations-button": {"a": "Japanese “congratulations” button", "b": "3297", "j": ["Japanese", "japanese", "kanji", "ideograph", "chinese", "red-circle", "“congratulations”", "祝"]}, "japanese-secret-button": {"a": "Japanese “secret” button", "b": "3299", "j": ["privacy", "“secret”", "Japanese", "sshh", "kanji", "ideograph", "秘", "red-circle", "chinese"]}, "japanese-open-for-business-button": {"a": "Japanese “open for business” button", "b": "1F23A", "j": ["orange-square", "Japanese", "営", "japanese", "opening hours", "“open for business”", "ideograph"]}, "japanese-no-vacancy-button": {"a": "Japanese “no vacancy” button", "b": "1F235", "j": ["満", "full", "Japanese", "japanese", "red-square", "kanji", "ideograph", "chinese", "“no vacancy”"]}, "red-circle": {"a": "red circle", "b": "1F534", "j": ["danger", "circle", "shape", "red", "geometric", "error"]}, "orange-circle": {"a": "orange circle", "b": "1F7E0", "j": ["circle", "orange", "round"]}, "yellow-circle": {"a": "yellow circle", "b": "1F7E1", "j": ["circle", "round", "yellow"]}, "green-circle": {"a": "green circle", "b": "1F7E2", "j": ["circle", "round", "green"]}, "blue-circle": {"a": "blue circle", "b": "1F535", "j": ["blue", "circle", "icon", "geometric", "shape", "button"]}, "purple-circle": {"a": "purple circle", "b": "1F7E3", "j": ["circle", "purple", "round"]}, "brown-circle": {"a": "brown circle", "b": "1F7E4", "j": ["circle", "brown", "round"]}, "black-circle": {"a": "black circle", "b": "26AB", "j": ["circle", "shape", "geometric", "button", "round"]}, "white-circle": {"a": "white circle", "b": "26AA", "j": ["circle", "shape", "round", "geometric"]}, "red-square": {"a": "red square", "b": "1F7E5", "j": ["red", "square"]}, "orange-square": {"a": "orange square", "b": "1F7E7", "j": ["square", "orange"]}, "yellow-square": {"a": "yellow square", "b": "1F7E8", "j": ["square", "yellow"]}, "green-square": {"a": "green square", "b": "1F7E9", "j": ["square", "green"]}, "blue-square": {"a": "blue square", "b": "1F7E6", "j": ["blue", "square"]}, "purple-square": {"a": "purple square", "b": "1F7EA", "j": ["square", "purple"]}, "brown-square": {"a": "brown square", "b": "1F7EB", "j": ["square", "brown"]}, "black-large-square": {"a": "black large square", "b": "2B1B", "j": ["icon", "geometric", "shape", "square", "button"]}, "white-large-square": {"a": "white large square", "b": "2B1C", "j": ["icon", "stone", "shape", "geometric", "square", "button"]}, "black-medium-square": {"a": "black medium square", "b": "25FC", "j": ["icon", "geometric", "shape", "square", "button"]}, "white-medium-square": {"a": "white medium square", "b": "25FB", "j": ["icon", "stone", "shape", "geometric", "square"]}, "black-mediumsmall-square": {"a": "black medium-small square", "b": "25FE", "j": ["icon", "geometric", "shape", "square", "black_medium_small_square", "button"]}, "white-mediumsmall-square": {"a": "white medium-small square", "b": "25FD", "j": ["icon", "stone", "geometric", "shape", "square", "white_medium_small_square", "button"]}, "black-small-square": {"a": "black small square", "b": "25AA", "j": ["geometric", "shape", "icon", "square"]}, "white-small-square": {"a": "white small square", "b": "25AB", "j": ["geometric", "shape", "icon", "square"]}, "large-orange-diamond": {"a": "large orange diamond", "b": "1F536", "j": ["diamond", "orange", "gem", "shape", "geometric", "jewel"]}, "large-blue-diamond": {"a": "large blue diamond", "b": "1F537", "j": ["blue", "diamond", "geometric", "shape", "gem", "jewel"]}, "small-orange-diamond": {"a": "small orange diamond", "b": "1F538", "j": ["diamond", "orange", "gem", "shape", "geometric", "jewel"]}, "small-blue-diamond": {"a": "small blue diamond", "b": "1F539", "j": ["blue", "diamond", "geometric", "shape", "gem", "jewel"]}, "red-triangle-pointed-up": {"a": "red triangle pointed up", "b": "1F53A", "j": ["top", "geometric", "shape", "red", "direction", "up"]}, "red-triangle-pointed-down": {"a": "red triangle pointed down", "b": "1F53B", "j": ["down", "shape", "red", "geometric", "direction", "bottom"]}, "diamond-with-a-dot": {"a": "diamond with a dot", "b": "1F4A0", "j": ["blue", "diamond", "fancy", "inside", "crystal", "comic", "geometric", "gem", "jewel"]}, "radio-button": {"a": "radio button", "b": "1F518", "j": ["circle", "input", "geometric", "old", "button", "music", "radio"]}, "white-square-button": {"a": "white square button", "b": "1F533", "j": ["input", "outlined", "geometric", "shape", "square", "button"]}, "black-square-button": {"a": "black square button", "b": "1F532", "j": ["input", "geometric", "shape", "square", "button", "frame"]}, "chequered-flag": {"a": "chequered flag", "b": "1F3C1", "j": ["checkered", "contest", "finishline", "race", "racing", "gokart", "chequered"]}, "triangular-flag": {"a": "triangular flag", "b": "1F6A9", "j": ["place", "post", "milestone", "mark"]}, "crossed-flags": {"a": "crossed flags", "b": "1F38C", "j": ["cross", "Japanese", "crossed", "japanese", "celebration", "nation", "country", "border"]}, "black-flag": {"a": "black flag", "b": "1F3F4", "j": ["waving", "pirate"]}, "white-flag": {"a": "white flag", "b": "1F3F3", "j": ["waving", "give up", "loser", "losing", "fail", "lost", "surrender"]}, "rainbow-flag": {"a": "rainbow flag", "b": "1F3F3-FE0F-200D-1F308", "j": ["homosexual", "glbt", "flag", "pride", "bisexual", "gay", "queer", "lesbian", "lgbt", "rainbow", "transgender"]}, "transgender-flag": {"a": "transgender flag", "b": "1F3F3-FE0F-200D-26A7-FE0F", "j": ["flag", "lgbtq", "light blue", "pink", "white", "transgender"]}, "pirate-flag": {"a": "pirate flag", "b": "1F3F4-200D-2620-FE0F", "j": ["banner", "pirate", "skull", "flag", "Jolly Roger", "treasure", "plunder", "crossbones"]}, "flag-ascension-island": {"a": "flag: Ascension Island", "b": "1F1E6-1F1E8", "j": ["flag"]}, "flag-andorra": {"a": "flag: Andorra", "b": "1F1E6-1F1E9", "j": ["banner", "ad", "flag", "country", "nation"]}, "flag-united-arab-emirates": {"a": "flag: United Arab Emirates", "b": "1F1E6-1F1EA", "j": ["united", "banner", "flag", "arab", "nation", "country", "emirates"]}, "flag-afghanistan": {"a": "flag: Afghanistan", "b": "1F1E6-1F1EB", "j": ["banner", "af", "flag", "country", "nation"]}, "flag-antigua--barbuda": {"a": "flag: Antigua & Barbuda", "b": "1F1E6-1F1EC", "j": ["banner", "antigua", "flag", "flag_antigua_barbuda", "nation", "country", "barbuda"]}, "flag-anguilla": {"a": "flag: Anguilla", "b": "1F1E6-1F1EE", "j": ["banner", "flag", "ai", "country", "nation"]}, "flag-albania": {"a": "flag: Albania", "b": "1F1E6-1F1F1", "j": ["banner", "flag", "nation", "country", "al"]}, "flag-armenia": {"a": "flag: Armenia", "b": "1F1E6-1F1F2", "j": ["banner", "am", "flag", "nation", "country"]}, "flag-angola": {"a": "flag: Angola", "b": "1F1E6-1F1F4", "j": ["banner", "flag", "ao", "country", "nation"]}, "flag-antarctica": {"a": "flag: Antarctica", "b": "1F1E6-1F1F6", "j": ["banner", "flag", "nation", "country", "aq"]}, "flag-argentina": {"a": "flag: Argentina", "b": "1F1E6-1F1F7", "j": ["banner", "flag", "ar", "nation", "country"]}, "flag-american-samoa": {"a": "flag: American Samoa", "b": "1F1E6-1F1F8", "j": ["banner", "flag", "ws", "nation", "country", "american"]}, "flag-austria": {"a": "flag: Austria", "b": "1F1E6-1F1F9", "j": ["banner", "at", "flag", "nation", "country"]}, "flag-australia": {"a": "flag: Australia", "b": "1F1E6-1F1FA", "j": ["banner", "flag", "au", "nation", "country"]}, "flag-aruba": {"a": "flag: Aruba", "b": "1F1E6-1F1FC", "j": ["banner", "flag", "nation", "country", "aw"]}, "flag-land-islands": {"a": "flag: Åland Islands", "b": "1F1E6-1F1FD", "j": ["banner", "Åland", "flag_aland_islands", "flag", "islands", "nation", "country"]}, "flag-azerbaijan": {"a": "flag: Azerbaijan", "b": "1F1E6-1F1FF", "j": ["banner", "az", "flag", "country", "nation"]}, "flag-bosnia--herzegovina": {"a": "flag: Bosnia & Herzegovina", "b": "1F1E7-1F1E6", "j": ["banner", "flag", "flag_bosnia_herzegovina", "herzegovina", "bosnia", "nation", "country"]}, "flag-barbados": {"a": "flag: Barbados", "b": "1F1E7-1F1E7", "j": ["banner", "bb", "flag", "country", "nation"]}, "flag-bangladesh": {"a": "flag: Bangladesh", "b": "1F1E7-1F1E9", "j": ["banner", "flag", "country", "nation", "bd"]}, "flag-belgium": {"a": "flag: Belgium", "b": "1F1E7-1F1EA", "j": ["banner", "flag", "country", "nation", "be"]}, "flag-burkina-faso": {"a": "flag: Burkina Faso", "b": "1F1E7-1F1EB", "j": ["banner", "flag", "faso", "nation", "country", "burkina"]}, "flag-bulgaria": {"a": "flag: Bulgaria", "b": "1F1E7-1F1EC", "j": ["banner", "flag", "bg", "country", "nation"]}, "flag-bahrain": {"a": "flag: Bahrain", "b": "1F1E7-1F1ED", "j": ["banner", "flag", "bh", "country", "nation"]}, "flag-burundi": {"a": "flag: Burundi", "b": "1F1E7-1F1EE", "j": ["banner", "flag", "bi", "country", "nation"]}, "flag-benin": {"a": "flag: Benin", "b": "1F1E7-1F1EF", "j": ["banner", "flag", "bj", "country", "nation"]}, "flag-st-barthlemy": {"a": "flag: St. Barthélemy", "b": "1F1E7-1F1F1", "j": ["banner", "flag", "flag_st_barthelemy", "nation", "country", "barthélemy", "saint"]}, "flag-bermuda": {"a": "flag: Bermuda", "b": "1F1E7-1F1F2", "j": ["banner", "bm", "flag", "country", "nation"]}, "flag-brunei": {"a": "flag: Brunei", "b": "1F1E7-1F1F3", "j": ["banner", "darussalam", "flag", "nation", "bn", "country"]}, "flag-bolivia": {"a": "flag: Bolivia", "b": "1F1E7-1F1F4", "j": ["banner", "flag", "country", "nation", "bo"]}, "flag-caribbean-netherlands": {"a": "flag: Caribbean Netherlands", "b": "1F1E7-1F1F6", "j": ["banner", "flag", "bonaire", "nation", "country"]}, "flag-brazil": {"a": "flag: Brazil", "b": "1F1E7-1F1F7", "j": ["banner", "flag", "br", "country", "nation"]}, "flag-bahamas": {"a": "flag: Bahamas", "b": "1F1E7-1F1F8", "j": ["banner", "flag", "country", "nation", "bs"]}, "flag-bhutan": {"a": "flag: Bhutan", "b": "1F1E7-1F1F9", "j": ["bt", "banner", "flag", "country", "nation"]}, "flag-bouvet-island": {"a": "flag: Bouvet Island", "b": "1F1E7-1F1FB", "j": ["norway", "flag"]}, "flag-botswana": {"a": "flag: Botswana", "b": "1F1E7-1F1FC", "j": ["banner", "flag", "bw", "country", "nation"]}, "flag-belarus": {"a": "flag: Belarus", "b": "1F1E7-1F1FE", "j": ["banner", "flag", "nation", "country", "by"]}, "flag-belize": {"a": "flag: Belize", "b": "1F1E7-1F1FF", "j": ["banner", "flag", "country", "bz", "nation"]}, "flag-canada": {"a": "flag: Canada", "b": "1F1E8-1F1E6", "j": ["banner", "flag", "ca", "country", "nation"]}, "flag-cocos-keeling-islands": {"a": "flag: Cocos (Keeling) Islands", "b": "1F1E8-1F1E8", "j": ["keeling", "banner", "flag", "cocos", "islands", "nation", "country", "flag_cocos_islands"]}, "flag-congo--kinshasa": {"a": "flag: Congo - Kinshasa", "b": "1F1E8-1F1E9", "j": ["banner", "flag_congo_kinshasa", "republic", "democratic", "flag", "congo", "nation", "country"]}, "flag-central-african-republic": {"a": "flag: Central African Republic", "b": "1F1E8-1F1EB", "j": ["banner", "republic", "flag", "nation", "african", "country", "central"]}, "flag-congo--brazzaville": {"a": "flag: Congo - Brazzaville", "b": "1F1E8-1F1EC", "j": ["banner", "flag", "congo", "flag_congo_brazzaville", "country", "nation"]}, "flag-switzerland": {"a": "flag: Switzerland", "b": "1F1E8-1F1ED", "j": ["banner", "ch", "flag", "country", "nation"]}, "flag-cte-divoire": {"a": "flag: Côte d’Ivoire", "b": "1F1E8-1F1EE", "j": ["banner", "coast", "flag", "country", "nation", "flag_cote_d_ivoire", "ivory"]}, "flag-cook-islands": {"a": "flag: Cook Islands", "b": "1F1E8-1F1F0", "j": ["banner", "flag", "islands", "nation", "country", "cook"]}, "flag-chile": {"a": "flag: Chile", "b": "1F1E8-1F1F1", "j": ["flag", "banner", "nation", "country"]}, "flag-cameroon": {"a": "flag: Cameroon", "b": "1F1E8-1F1F2", "j": ["banner", "flag", "cm", "nation", "country"]}, "flag-china": {"a": "flag: China", "b": "1F1E8-1F1F3", "j": ["china", "banner", "flag", "prc", "country", "chinese", "nation"]}, "flag-colombia": {"a": "flag: Colombia", "b": "1F1E8-1F1F4", "j": ["banner", "co", "flag", "nation", "country"]}, "flag-clipperton-island": {"a": "flag: Clipperton Island", "b": "1F1E8-1F1F5", "j": ["flag"]}, "flag-costa-rica": {"a": "flag: Costa Rica", "b": "1F1E8-1F1F7", "j": ["banner", "flag", "nation", "rica", "costa", "country"]}, "flag-cuba": {"a": "flag: Cuba", "b": "1F1E8-1F1FA", "j": ["banner", "cu", "flag", "nation", "country"]}, "flag-cape-verde": {"a": "flag: Cape Verde", "b": "1F1E8-1F1FB", "j": ["banner", "flag", "cabo", "verde", "nation", "country"]}, "flag-curaao": {"a": "flag: Curaçao", "b": "1F1E8-1F1FC", "j": ["banner", "flag", "curaçao", "country", "flag_curacao", "nation"]}, "flag-christmas-island": {"a": "flag: Christmas Island", "b": "1F1E8-1F1FD", "j": ["banner", "flag", "nation", "country", "island", "christmas"]}, "flag-cyprus": {"a": "flag: Cyprus", "b": "1F1E8-1F1FE", "j": ["banner", "cy", "flag", "country", "nation"]}, "flag-czechia": {"a": "flag: Czechia", "b": "1F1E8-1F1FF", "j": ["cz", "banner", "flag", "nation", "country"]}, "flag-germany": {"a": "flag: Germany", "b": "1F1E9-1F1EA", "j": ["banner", "flag", "german", "country", "nation"]}, "flag-diego-garcia": {"a": "flag: Diego Garcia", "b": "1F1E9-1F1EC", "j": ["flag"]}, "flag-djibouti": {"a": "flag: Djibouti", "b": "1F1E9-1F1EF", "j": ["banner", "flag", "dj", "country", "nation"]}, "flag-denmark": {"a": "flag: Denmark", "b": "1F1E9-1F1F0", "j": ["banner", "flag", "dk", "country", "nation"]}, "flag-dominica": {"a": "flag: Dominica", "b": "1F1E9-1F1F2", "j": ["banner", "dm", "flag", "country", "nation"]}, "flag-dominican-republic": {"a": "flag: Dominican Republic", "b": "1F1E9-1F1F4", "j": ["banner", "republic", "flag", "dominican", "nation", "country"]}, "flag-algeria": {"a": "flag: Algeria", "b": "1F1E9-1F1FF", "j": ["banner", "flag", "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": ["banner", "flag", "ec", "country", "nation"]}, "flag-estonia": {"a": "flag: Estonia", "b": "1F1EA-1F1EA", "j": ["ee", "banner", "flag", "nation", "country"]}, "flag-egypt": {"a": "flag: Egypt", "b": "1F1EA-1F1EC", "j": ["banner", "flag", "eg", "country", "nation"]}, "flag-western-sahara": {"a": "flag: Western Sahara", "b": "1F1EA-1F1ED", "j": ["banner", "flag", "sahara", "nation", "country", "western"]}, "flag-eritrea": {"a": "flag: Eritrea", "b": "1F1EA-1F1F7", "j": ["banner", "flag", "er", "country", "nation"]}, "flag-spain": {"a": "flag: Spain", "b": "1F1EA-1F1F8", "j": ["spain", "banner", "flag", "country", "nation"]}, "flag-ethiopia": {"a": "flag: Ethiopia", "b": "1F1EA-1F1F9", "j": ["banner", "flag", "et", "nation", "country"]}, "flag-european-union": {"a": "flag: European Union", "b": "1F1EA-1F1FA", "j": ["banner", "european", "flag", "union"]}, "flag-finland": {"a": "flag: Finland", "b": "1F1EB-1F1EE", "j": ["banner", "fi", "flag", "nation", "country"]}, "flag-fiji": {"a": "flag: Fiji", "b": "1F1EB-1F1EF", "j": ["banner", "fj", "flag", "country", "nation"]}, "flag-falkland-islands": {"a": "flag: Falkland Islands", "b": "1F1EB-1F1F0", "j": ["banner", "flag", "malvinas", "falkland", "islands", "nation", "country"]}, "flag-micronesia": {"a": "flag: Micronesia", "b": "1F1EB-1F1F2", "j": ["banner", "flag", "federated", "micronesia", "nation", "country", "states"]}, "flag-faroe-islands": {"a": "flag: Faroe Islands", "b": "1F1EB-1F1F4", "j": ["banner", "flag", "islands", "nation", "country", "faroe"]}, "flag-france": {"a": "flag: France", "b": "1F1EB-1F1F7", "j": ["banner", "flag", "french", "france", "nation", "country"]}, "flag-gabon": {"a": "flag: Gabon", "b": "1F1EC-1F1E6", "j": ["banner", "flag", "ga", "country", "nation"]}, "flag-united-kingdom": {"a": "flag: United Kingdom", "b": "1F1EC-1F1E7", "j": ["united", "banner", "UK", "england", "ireland", "flag", "british", "britain", "english", "northern", "great", "kingdom", "nation", "country", "union jack"]}, "flag-grenada": {"a": "flag: Grenada", "b": "1F1EC-1F1E9", "j": ["banner", "flag", "gd", "country", "nation"]}, "flag-georgia": {"a": "flag: Georgia", "b": "1F1EC-1F1EA", "j": ["banner", "flag", "ge", "country", "nation"]}, "flag-french-guiana": {"a": "flag: French Guiana", "b": "1F1EC-1F1EB", "j": ["banner", "flag", "french", "guiana", "nation", "country"]}, "flag-guernsey": {"a": "flag: Guernsey", "b": "1F1EC-1F1EC", "j": ["banner", "gg", "flag", "country", "nation"]}, "flag-ghana": {"a": "flag: Ghana", "b": "1F1EC-1F1ED", "j": ["banner", "flag", "country", "nation", "gh"]}, "flag-gibraltar": {"a": "flag: Gibraltar", "b": "1F1EC-1F1EE", "j": ["banner", "flag", "nation", "country", "gi"]}, "flag-greenland": {"a": "flag: Greenland", "b": "1F1EC-1F1F1", "j": ["banner", "flag", "country", "nation", "gl"]}, "flag-gambia": {"a": "flag: Gambia", "b": "1F1EC-1F1F2", "j": ["banner", "gm", "flag", "country", "nation"]}, "flag-guinea": {"a": "flag: Guinea", "b": "1F1EC-1F1F3", "j": ["banner", "flag", "gn", "country", "nation"]}, "flag-guadeloupe": {"a": "flag: Guadeloupe", "b": "1F1EC-1F1F5", "j": ["banner", "flag", "country", "gp", "nation"]}, "flag-equatorial-guinea": {"a": "flag: Equatorial Guinea", "b": "1F1EC-1F1F6", "j": ["banner", "flag", "gn", "equatorial", "nation", "country"]}, "flag-greece": {"a": "flag: Greece", "b": "1F1EC-1F1F7", "j": ["banner", "flag", "gr", "nation", "country"]}, "flag-south-georgia--south-sandwich-islands": {"a": "flag: South Georgia & South Sandwich Islands", "b": "1F1EC-1F1F8", "j": ["banner", "flag", "flag_south_georgia_south_sandwich_islands", "islands", "georgia", "nation", "country", "sandwich", "south"]}, "flag-guatemala": {"a": "flag: Guatemala", "b": "1F1EC-1F1F9", "j": ["banner", "flag", "gt", "country", "nation"]}, "flag-guam": {"a": "flag: Guam", "b": "1F1EC-1F1FA", "j": ["banner", "flag", "nation", "country", "gu"]}, "flag-guineabissau": {"a": "flag: Guinea-Bissau", "b": "1F1EC-1F1FC", "j": ["banner", "flag_guinea_bissau", "flag", "nation", "country", "bissau", "gw"]}, "flag-guyana": {"a": "flag: Guyana", "b": "1F1EC-1F1FE", "j": ["banner", "flag", "gy", "country", "nation"]}, "flag-hong-kong-sar-china": {"a": "flag: Hong Kong SAR China", "b": "1F1ED-1F1F0", "j": ["banner", "kong", "flag", "hong", "nation", "country"]}, "flag-heard--mcdonald-islands": {"a": "flag: Heard & McDonald Islands", "b": "1F1ED-1F1F2", "j": ["flag_heard_mcdonald_islands", "flag"]}, "flag-honduras": {"a": "flag: Honduras", "b": "1F1ED-1F1F3", "j": ["banner", "flag", "hn", "nation", "country"]}, "flag-croatia": {"a": "flag: Croatia", "b": "1F1ED-1F1F7", "j": ["banner", "flag", "country", "nation", "hr"]}, "flag-haiti": {"a": "flag: Haiti", "b": "1F1ED-1F1F9", "j": ["banner", "flag", "country", "nation", "ht"]}, "flag-hungary": {"a": "flag: Hungary", "b": "1F1ED-1F1FA", "j": ["banner", "flag", "hu", "country", "nation"]}, "flag-canary-islands": {"a": "flag: Canary Islands", "b": "1F1EE-1F1E8", "j": ["banner", "flag", "canary", "islands", "nation", "country"]}, "flag-indonesia": {"a": "flag: Indonesia", "b": "1F1EE-1F1E9", "j": ["flag", "banner", "nation", "country"]}, "flag-ireland": {"a": "flag: Ireland", "b": "1F1EE-1F1EA", "j": ["banner", "flag", "ie", "nation", "country"]}, "flag-israel": {"a": "flag: Israel", "b": "1F1EE-1F1F1", "j": ["banner", "flag", "il", "country", "nation"]}, "flag-isle-of-man": {"a": "flag: Isle of Man", "b": "1F1EE-1F1F2", "j": ["isle", "banner", "man", "flag", "nation", "country"]}, "flag-india": {"a": "flag: India", "b": "1F1EE-1F1F3", "j": ["banner", "flag", "in", "country", "nation"]}, "flag-british-indian-ocean-territory": {"a": "flag: British Indian Ocean Territory", "b": "1F1EE-1F1F4", "j": ["banner", "flag", "british", "ocean", "nation", "territory", "country", "indian"]}, "flag-iraq": {"a": "flag: Iraq", "b": "1F1EE-1F1F6", "j": ["banner", "iq", "flag", "nation", "country"]}, "flag-iran": {"a": "flag: Iran", "b": "1F1EE-1F1F7", "j": ["banner", "republic", "flag", "iran", "islamic", "nation", "country"]}, "flag-iceland": {"a": "flag: Iceland", "b": "1F1EE-1F1F8", "j": ["banner", "flag", "is", "country", "nation"]}, "flag-italy": {"a": "flag: Italy", "b": "1F1EE-1F1F9", "j": ["banner", "flag", "italy", "country", "nation"]}, "flag-jersey": {"a": "flag: Jersey", "b": "1F1EF-1F1EA", "j": ["je", "banner", "flag", "nation", "country"]}, "flag-jamaica": {"a": "flag: Jamaica", "b": "1F1EF-1F1F2", "j": ["banner", "flag", "nation", "country", "jm"]}, "flag-jordan": {"a": "flag: Jordan", "b": "1F1EF-1F1F4", "j": ["jo", "banner", "flag", "nation", "country"]}, "flag-japan": {"a": "flag: Japan", "b": "1F1EF-1F1F5", "j": ["banner", "flag", "japanese", "nation", "country"]}, "flag-kenya": {"a": "flag: Kenya", "b": "1F1F0-1F1EA", "j": ["banner", "flag", "country", "nation", "ke"]}, "flag-kyrgyzstan": {"a": "flag: Kyrgyzstan", "b": "1F1F0-1F1EC", "j": ["kg", "banner", "flag", "nation", "country"]}, "flag-cambodia": {"a": "flag: Cambodia", "b": "1F1F0-1F1ED", "j": ["banner", "flag", "kh", "nation", "country"]}, "flag-kiribati": {"a": "flag: Kiribati", "b": "1F1F0-1F1EE", "j": ["banner", "flag", "nation", "country", "ki"]}, "flag-comoros": {"a": "flag: Comoros", "b": "1F1F0-1F1F2", "j": ["banner", "flag", "km", "country", "nation"]}, "flag-st-kitts--nevis": {"a": "flag: St. Kitts & Nevis", "b": "1F1F0-1F1F3", "j": ["nevis", "banner", "kitts", "flag", "flag_st_kitts_nevis", "nation", "country", "saint"]}, "flag-north-korea": {"a": "flag: North Korea", "b": "1F1F0-1F1F5", "j": ["banner", "flag", "korea", "country", "nation", "north"]}, "flag-south-korea": {"a": "flag: South Korea", "b": "1F1F0-1F1F7", "j": ["banner", "flag", "korea", "nation", "country", "south"]}, "flag-kuwait": {"a": "flag: Kuwait", "b": "1F1F0-1F1FC", "j": ["banner", "flag", "kw", "nation", "country"]}, "flag-cayman-islands": {"a": "flag: Cayman Islands", "b": "1F1F0-1F1FE", "j": ["banner", "flag", "cayman", "islands", "nation", "country"]}, "flag-kazakhstan": {"a": "flag: Kazakhstan", "b": "1F1F0-1F1FF", "j": ["kz", "banner", "flag", "nation", "country"]}, "flag-laos": {"a": "flag: Laos", "b": "1F1F1-1F1E6", "j": ["banner", "republic", "flag", "democratic", "nation", "country", "lao"]}, "flag-lebanon": {"a": "flag: Lebanon", "b": "1F1F1-1F1E7", "j": ["banner", "flag", "lb", "country", "nation"]}, "flag-st-lucia": {"a": "flag: St. Lucia", "b": "1F1F1-1F1E8", "j": ["banner", "flag", "lucia", "nation", "country", "saint"]}, "flag-liechtenstein": {"a": "flag: Liechtenstein", "b": "1F1F1-1F1EE", "j": ["banner", "flag", "country", "nation", "li"]}, "flag-sri-lanka": {"a": "flag: Sri Lanka", "b": "1F1F1-1F1F0", "j": ["lanka", "banner", "flag", "nation", "country", "sri"]}, "flag-liberia": {"a": "flag: Liberia", "b": "1F1F1-1F1F7", "j": ["banner", "flag", "lr", "country", "nation"]}, "flag-lesotho": {"a": "flag: Lesotho", "b": "1F1F1-1F1F8", "j": ["banner", "flag", "country", "nation", "ls"]}, "flag-lithuania": {"a": "flag: Lithuania", "b": "1F1F1-1F1F9", "j": ["banner", "flag", "lt", "country", "nation"]}, "flag-luxembourg": {"a": "flag: Luxembourg", "b": "1F1F1-1F1FA", "j": ["banner", "flag", "country", "nation", "lu"]}, "flag-latvia": {"a": "flag: Latvia", "b": "1F1F1-1F1FB", "j": ["lv", "banner", "flag", "nation", "country"]}, "flag-libya": {"a": "flag: Libya", "b": "1F1F1-1F1FE", "j": ["banner", "ly", "flag", "country", "nation"]}, "flag-morocco": {"a": "flag: Morocco", "b": "1F1F2-1F1E6", "j": ["banner", "ma", "flag", "country", "nation"]}, "flag-monaco": {"a": "flag: Monaco", "b": "1F1F2-1F1E8", "j": ["banner", "flag", "mc", "nation", "country"]}, "flag-moldova": {"a": "flag: Moldova", "b": "1F1F2-1F1E9", "j": ["banner", "republic", "flag", "country", "nation", "moldova"]}, "flag-montenegro": {"a": "flag: Montenegro", "b": "1F1F2-1F1EA", "j": ["banner", "flag", "me", "country", "nation"]}, "flag-st-martin": {"a": "flag: St. Martin", "b": "1F1F2-1F1EB", "j": ["flag"]}, "flag-madagascar": {"a": "flag: Madagascar", "b": "1F1F2-1F1EC", "j": ["banner", "flag", "country", "nation", "mg"]}, "flag-marshall-islands": {"a": "flag: Marshall Islands", "b": "1F1F2-1F1ED", "j": ["banner", "flag", "islands", "nation", "country", "marshall"]}, "flag-north-macedonia": {"a": "flag: North Macedonia", "b": "1F1F2-1F1F0", "j": ["banner", "flag", "macedonia", "country", "nation"]}, "flag-mali": {"a": "flag: Mali", "b": "1F1F2-1F1F1", "j": ["banner", "flag", "country", "ml", "nation"]}, "flag-myanmar-burma": {"a": "flag: Myanmar (Burma)", "b": "1F1F2-1F1F2", "j": ["banner", "flag", "flag_myanmar", "mm", "country", "nation"]}, "flag-mongolia": {"a": "flag: Mongolia", "b": "1F1F2-1F1F3", "j": ["banner", "flag", "mn", "country", "nation"]}, "flag-macao-sar-china": {"a": "flag: Macao SAR China", "b": "1F1F2-1F1F4", "j": ["banner", "flag", "country", "macao", "nation"]}, "flag-northern-mariana-islands": {"a": "flag: Northern Mariana Islands", "b": "1F1F2-1F1F5", "j": ["banner", "flag", "northern", "islands", "nation", "country", "mariana"]}, "flag-martinique": {"a": "flag: Martinique", "b": "1F1F2-1F1F6", "j": ["banner", "flag", "country", "nation", "mq"]}, "flag-mauritania": {"a": "flag: Mauritania", "b": "1F1F2-1F1F7", "j": ["banner", "mr", "flag", "country", "nation"]}, "flag-montserrat": {"a": "flag: Montserrat", "b": "1F1F2-1F1F8", "j": ["banner", "flag", "ms", "country", "nation"]}, "flag-malta": {"a": "flag: Malta", "b": "1F1F2-1F1F9", "j": ["banner", "mt", "flag", "country", "nation"]}, "flag-mauritius": {"a": "flag: Mauritius", "b": "1F1F2-1F1FA", "j": ["banner", "flag", "country", "nation", "mu"]}, "flag-maldives": {"a": "flag: Maldives", "b": "1F1F2-1F1FB", "j": ["banner", "mv", "flag", "country", "nation"]}, "flag-malawi": {"a": "flag: Malawi", "b": "1F1F2-1F1FC", "j": ["banner", "flag", "country", "nation", "mw"]}, "flag-mexico": {"a": "flag: Mexico", "b": "1F1F2-1F1FD", "j": ["banner", "flag", "mx", "nation", "country"]}, "flag-malaysia": {"a": "flag: Malaysia", "b": "1F1F2-1F1FE", "j": ["banner", "flag", "my", "country", "nation"]}, "flag-mozambique": {"a": "flag: Mozambique", "b": "1F1F2-1F1FF", "j": ["banner", "flag", "country", "nation", "mz"]}, "flag-namibia": {"a": "flag: Namibia", "b": "1F1F3-1F1E6", "j": ["banner", "flag", "nation", "country", "na"]}, "flag-new-caledonia": {"a": "flag: New Caledonia", "b": "1F1F3-1F1E8", "j": ["banner", "flag", "new", "nation", "country", "caledonia"]}, "flag-niger": {"a": "flag: Niger", "b": "1F1F3-1F1EA", "j": ["banner", "flag", "ne", "nation", "country"]}, "flag-norfolk-island": {"a": "flag: Norfolk Island", "b": "1F1F3-1F1EB", "j": ["banner", "flag", "nation", "country", "norfolk", "island"]}, "flag-nigeria": {"a": "flag: Nigeria", "b": "1F1F3-1F1EC", "j": ["flag", "banner", "nation", "country"]}, "flag-nicaragua": {"a": "flag: Nicaragua", "b": "1F1F3-1F1EE", "j": ["banner", "flag", "ni", "country", "nation"]}, "flag-netherlands": {"a": "flag: Netherlands", "b": "1F1F3-1F1F1", "j": ["banner", "flag", "nl", "country", "nation"]}, "flag-norway": {"a": "flag: Norway", "b": "1F1F3-1F1F4", "j": ["banner", "flag", "no", "country", "nation"]}, "flag-nepal": {"a": "flag: Nepal", "b": "1F1F3-1F1F5", "j": ["banner", "flag", "country", "nation", "np"]}, "flag-nauru": {"a": "flag: Nauru", "b": "1F1F3-1F1F7", "j": ["banner", "nr", "flag", "nation", "country"]}, "flag-niue": {"a": "flag: Niue", "b": "1F1F3-1F1FA", "j": ["banner", "flag", "nu", "country", "nation"]}, "flag-new-zealand": {"a": "flag: New Zealand", "b": "1F1F3-1F1FF", "j": ["banner", "flag", "zealand", "nation", "country", "new"]}, "flag-oman": {"a": "flag: Oman", "b": "1F1F4-1F1F2", "j": ["banner", "flag", "nation", "country", "om_symbol"]}, "flag-panama": {"a": "flag: Panama", "b": "1F1F5-1F1E6", "j": ["banner", "flag", "pa", "country", "nation"]}, "flag-peru": {"a": "flag: Peru", "b": "1F1F5-1F1EA", "j": ["banner", "flag", "country", "nation", "pe"]}, "flag-french-polynesia": {"a": "flag: French Polynesia", "b": "1F1F5-1F1EB", "j": ["banner", "flag", "french", "polynesia", "nation", "country"]}, "flag-papua-new-guinea": {"a": "flag: Papua New Guinea", "b": "1F1F5-1F1EC", "j": ["banner", "flag", "papua", "nation", "country", "new", "guinea"]}, "flag-philippines": {"a": "flag: Philippines", "b": "1F1F5-1F1ED", "j": ["banner", "flag", "ph", "country", "nation"]}, "flag-pakistan": {"a": "flag: Pakistan", "b": "1F1F5-1F1F0", "j": ["banner", "flag", "pk", "country", "nation"]}, "flag-poland": {"a": "flag: Poland", "b": "1F1F5-1F1F1", "j": ["banner", "pl", "flag", "country", "nation"]}, "flag-st-pierre--miquelon": {"a": "flag: St. Pierre & Miquelon", "b": "1F1F5-1F1F2", "j": ["banner", "flag", "flag_st_pierre_miquelon", "miquelon", "nation", "country", "saint", "pierre"]}, "flag-pitcairn-islands": {"a": "flag: Pitcairn Islands", "b": "1F1F5-1F1F3", "j": ["banner", "flag", "nation", "country", "pitcairn"]}, "flag-puerto-rico": {"a": "flag: Puerto Rico", "b": "1F1F5-1F1F7", "j": ["banner", "puerto", "flag", "nation", "country", "rico"]}, "flag-palestinian-territories": {"a": "flag: Palestinian Territories", "b": "1F1F5-1F1F8", "j": ["banner", "flag", "palestinian", "territories", "nation", "country", "palestine"]}, "flag-portugal": {"a": "flag: Portugal", "b": "1F1F5-1F1F9", "j": ["banner", "flag", "country", "nation", "pt"]}, "flag-palau": {"a": "flag: Palau", "b": "1F1F5-1F1FC", "j": ["banner", "flag", "country", "nation", "pw"]}, "flag-paraguay": {"a": "flag: Paraguay", "b": "1F1F5-1F1FE", "j": ["banner", "flag", "py", "nation", "country"]}, "flag-qatar": {"a": "flag: Qatar", "b": "1F1F6-1F1E6", "j": ["banner", "flag", "country", "nation", "qa"]}, "flag-runion": {"a": "flag: Réunion", "b": "1F1F7-1F1EA", "j": ["banner", "flag", "réunion", "country", "nation", "flag_reunion"]}, "flag-romania": {"a": "flag: Romania", "b": "1F1F7-1F1F4", "j": ["banner", "flag", "nation", "country", "ro"]}, "flag-serbia": {"a": "flag: Serbia", "b": "1F1F7-1F1F8", "j": ["banner", "flag", "rs", "country", "nation"]}, "flag-russia": {"a": "flag: Russia", "b": "1F1F7-1F1FA", "j": ["banner", "russian", "flag", "federation", "nation", "country"]}, "flag-rwanda": {"a": "flag: Rwanda", "b": "1F1F7-1F1FC", "j": ["banner", "flag", "country", "nation", "rw"]}, "flag-saudi-arabia": {"a": "flag: Saudi Arabia", "b": "1F1F8-1F1E6", "j": ["flag", "banner", "nation", "country"]}, "flag-solomon-islands": {"a": "flag: Solomon Islands", "b": "1F1F8-1F1E7", "j": ["banner", "flag", "islands", "nation", "country", "solomon"]}, "flag-seychelles": {"a": "flag: Seychelles", "b": "1F1F8-1F1E8", "j": ["banner", "flag", "sc", "nation", "country"]}, "flag-sudan": {"a": "flag: Sudan", "b": "1F1F8-1F1E9", "j": ["banner", "sd", "flag", "country", "nation"]}, "flag-sweden": {"a": "flag: Sweden", "b": "1F1F8-1F1EA", "j": ["banner", "flag", "se", "country", "nation"]}, "flag-singapore": {"a": "flag: Singapore", "b": "1F1F8-1F1EC", "j": ["banner", "flag", "country", "nation", "sg"]}, "flag-st-helena": {"a": "flag: St. Helena", "b": "1F1F8-1F1ED", "j": ["banner", "flag", "cunha", "ascension", "helena", "nation", "country", "saint", "tristan"]}, "flag-slovenia": {"a": "flag: Slovenia", "b": "1F1F8-1F1EE", "j": ["banner", "flag", "nation", "si", "country"]}, "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": ["sk", "banner", "flag", "nation", "country"]}, "flag-sierra-leone": {"a": "flag: Sierra Leone", "b": "1F1F8-1F1F1", "j": ["banner", "flag", "sierra", "leone", "nation", "country"]}, "flag-san-marino": {"a": "flag: San Marino", "b": "1F1F8-1F1F2", "j": ["banner", "flag", "san", "nation", "country", "marino"]}, "flag-senegal": {"a": "flag: Senegal", "b": "1F1F8-1F1F3", "j": ["banner", "flag", "nation", "sn", "country"]}, "flag-somalia": {"a": "flag: Somalia", "b": "1F1F8-1F1F4", "j": ["banner", "so", "flag", "country", "nation"]}, "flag-suriname": {"a": "flag: Suriname", "b": "1F1F8-1F1F7", "j": ["banner", "flag", "sr", "country", "nation"]}, "flag-south-sudan": {"a": "flag: South Sudan", "b": "1F1F8-1F1F8", "j": ["banner", "sd", "flag", "nation", "country", "south"]}, "flag-so-tom--prncipe": {"a": "flag: São Tomé & Príncipe", "b": "1F1F8-1F1F9", "j": ["tome", "sao", "banner", "flag", "principe", "nation", "flag_sao_tome_principe", "country"]}, "flag-el-salvador": {"a": "flag: El Salvador", "b": "1F1F8-1F1FB", "j": ["banner", "flag", "nation", "country", "salvador", "el"]}, "flag-sint-maarten": {"a": "flag: Sint Maarten", "b": "1F1F8-1F1FD", "j": ["banner", "flag", "maarten", "dutch", "sint", "nation", "country"]}, "flag-syria": {"a": "flag: Syria", "b": "1F1F8-1F1FE", "j": ["banner", "republic", "flag", "syrian", "arab", "nation", "country"]}, "flag-eswatini": {"a": "flag: Eswatini", "b": "1F1F8-1F1FF", "j": ["banner", "flag", "sz", "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": ["banner", "caicos", "flag", "islands", "nation", "country", "turks", "flag_turks_caicos_islands"]}, "flag-chad": {"a": "flag: Chad", "b": "1F1F9-1F1E9", "j": ["banner", "td", "flag", "country", "nation"]}, "flag-french-southern-territories": {"a": "flag: French Southern Territories", "b": "1F1F9-1F1EB", "j": ["banner", "flag", "french", "southern", "territories", "nation", "country"]}, "flag-togo": {"a": "flag: Togo", "b": "1F1F9-1F1EC", "j": ["banner", "flag", "tg", "country", "nation"]}, "flag-thailand": {"a": "flag: Thailand", "b": "1F1F9-1F1ED", "j": ["banner", "flag", "country", "nation", "th"]}, "flag-tajikistan": {"a": "flag: Tajikistan", "b": "1F1F9-1F1EF", "j": ["banner", "flag", "tj", "country", "nation"]}, "flag-tokelau": {"a": "flag: Tokelau", "b": "1F1F9-1F1F0", "j": ["tk", "banner", "flag", "country", "nation"]}, "flag-timorleste": {"a": "flag: Timor-Leste", "b": "1F1F9-1F1F1", "j": ["leste", "banner", "timor", "flag", "nation", "country", "flag_timor_leste"]}, "flag-turkmenistan": {"a": "flag: Turkmenistan", "b": "1F1F9-1F1F2", "j": ["flag", "banner", "country", "nation"]}, "flag-tunisia": {"a": "flag: Tunisia", "b": "1F1F9-1F1F3", "j": ["banner", "flag", "country", "nation", "tn"]}, "flag-tonga": {"a": "flag: Tonga", "b": "1F1F9-1F1F4", "j": ["banner", "flag", "country", "nation", "to"]}, "flag-turkey": {"a": "flag: Turkey", "b": "1F1F9-1F1F7", "j": ["banner", "flag", "nation", "turkey", "country"]}, "flag-trinidad--tobago": {"a": "flag: Trinidad & Tobago", "b": "1F1F9-1F1F9", "j": ["banner", "flag_trinidad_tobago", "flag", "trinidad", "nation", "country", "tobago"]}, "flag-tuvalu": {"a": "flag: Tuvalu", "b": "1F1F9-1F1FB", "j": ["flag", "banner", "nation", "country"]}, "flag-taiwan": {"a": "flag: Taiwan", "b": "1F1F9-1F1FC", "j": ["banner", "flag", "country", "nation", "tw"]}, "flag-tanzania": {"a": "flag: Tanzania", "b": "1F1F9-1F1FF", "j": ["united", "banner", "republic", "flag", "nation", "country", "tanzania"]}, "flag-ukraine": {"a": "flag: Ukraine", "b": "1F1FA-1F1E6", "j": ["banner", "flag", "ua", "country", "nation"]}, "flag-uganda": {"a": "flag: Uganda", "b": "1F1FA-1F1EC", "j": ["banner", "flag", "ug", "country", "nation"]}, "flag-us-outlying-islands": {"a": "flag: U.S. Outlying Islands", "b": "1F1FA-1F1F2", "j": ["flag_u_s_outlying_islands", "flag"]}, "flag-united-nations": {"a": "flag: United Nations", "b": "1F1FA-1F1F3", "j": ["banner", "un", "flag"]}, "flag-united-states": {"a": "flag: United States", "b": "1F1FA-1F1F8", "j": ["united", "banner", "flag", "america", "nation", "country", "states"]}, "flag-uruguay": {"a": "flag: Uruguay", "b": "1F1FA-1F1FE", "j": ["banner", "flag", "uy", "country", "nation"]}, "flag-uzbekistan": {"a": "flag: Uzbekistan", "b": "1F1FA-1F1FF", "j": ["banner", "uz", "flag", "country", "nation"]}, "flag-vatican-city": {"a": "flag: Vatican City", "b": "1F1FB-1F1E6", "j": ["banner", "flag", "vatican", "city", "nation", "country"]}, "flag-st-vincent--grenadines": {"a": "flag: St. Vincent & Grenadines", "b": "1F1FB-1F1E8", "j": ["banner", "flag", "flag_st_vincent_grenadines", "grenadines", "nation", "country", "vincent", "saint"]}, "flag-venezuela": {"a": "flag: Venezuela", "b": "1F1FB-1F1EA", "j": ["banner", "republic", "flag", "bolivarian", "ve", "nation", "country"]}, "flag-british-virgin-islands": {"a": "flag: British Virgin Islands", "b": "1F1FB-1F1EC", "j": ["banner", "virgin", "bvi", "flag", "british", "islands", "nation", "country"]}, "flag-us-virgin-islands": {"a": "flag: U.S. Virgin Islands", "b": "1F1FB-1F1EE", "j": ["banner", "virgin", "flag", "us", "islands", "nation", "country", "flag_u_s_virgin_islands"]}, "flag-vietnam": {"a": "flag: Vietnam", "b": "1F1FB-1F1F3", "j": ["banner", "flag", "nation", "nam", "country", "viet"]}, "flag-vanuatu": {"a": "flag: Vanuatu", "b": "1F1FB-1F1FA", "j": ["banner", "flag", "vu", "country", "nation"]}, "flag-wallis--futuna": {"a": "flag: Wallis & Futuna", "b": "1F1FC-1F1EB", "j": ["banner", "flag_wallis_futuna", "flag", "wallis", "nation", "country", "futuna"]}, "flag-samoa": {"a": "flag: Samoa", "b": "1F1FC-1F1F8", "j": ["banner", "flag", "ws", "nation", "country"]}, "flag-kosovo": {"a": "flag: Kosovo", "b": "1F1FD-1F1F0", "j": ["banner", "flag", "country", "nation", "xk"]}, "flag-yemen": {"a": "flag: Yemen", "b": "1F1FE-1F1EA", "j": ["banner", "flag", "nation", "ye", "country"]}, "flag-mayotte": {"a": "flag: Mayotte", "b": "1F1FE-1F1F9", "j": ["banner", "flag", "yt", "country", "nation"]}, "flag-south-africa": {"a": "flag: South Africa", "b": "1F1FF-1F1E6", "j": ["banner", "africa", "flag", "nation", "country", "south"]}, "flag-zambia": {"a": "flag: Zambia", "b": "1F1FF-1F1F2", "j": ["banner", "flag", "zm", "country", "nation"]}, "flag-zimbabwe": {"a": "flag: Zimbabwe", "b": "1F1FF-1F1FC", "j": ["banner", "flag", "zw", "country", "nation"]}, "flag-england": {"a": "flag: England", "b": "1F3F4-E0067-E0062-E0065-E006E-E0067-E007F", "j": ["english", "flag"]}, "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": ["welsh", "flag"]}}, "aliases": {}} \ No newline at end of file From 2bdb67cd4d460be54d0f1091ac08724887859879 Mon Sep 17 00:00:00 2001 From: ozzii Date: Sat, 20 Mar 2021 14:57:17 +0000 Subject: [PATCH 073/249] Translated using Weblate (Serbian) Currently translated at 23.0% (545 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sr/ --- vector/src/main/res/values-sr/strings.xml | 157 ++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/vector/src/main/res/values-sr/strings.xml b/vector/src/main/res/values-sr/strings.xml index 2ce03e4a54..b44a7f78c5 100644 --- a/vector/src/main/res/values-sr/strings.xml +++ b/vector/src/main/res/values-sr/strings.xml @@ -450,4 +450,161 @@ Ресет Одбаци Паузирај + Неуспешно иницијализовање камере + Веза није успела + Дописник није одговорио. + Поставили сте позив на чекање + %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 + Permalink + Почетна синхронизација: +\nПреузимање података… + Почетна синхронизација: +\nЧека се одговор сервера… \ No newline at end of file From beb552db3144f905dc96aedf7e153d103c2a620b Mon Sep 17 00:00:00 2001 From: GokdenizK Date: Fri, 19 Mar 2021 12:12:01 +0000 Subject: [PATCH 074/249] Translated using Weblate (Turkish) Currently translated at 66.2% (1564 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/tr/ --- vector/src/main/res/values-tr/strings.xml | 35 +++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/vector/src/main/res/values-tr/strings.xml b/vector/src/main/res/values-tr/strings.xml index e5773a99a1..6a065d06e8 100644 --- a/vector/src/main/res/values-tr/strings.xml +++ b/vector/src/main/res/values-tr/strings.xml @@ -1742,4 +1742,39 @@ 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ı. + " +\n" + 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ı \ No newline at end of file From b117c30e4422cac2cffe5180c7ffa7aa8175c618 Mon Sep 17 00:00:00 2001 From: vachan-maker Date: Sat, 20 Mar 2021 04:27:22 +0000 Subject: [PATCH 075/249] Translated using Weblate (Malayalam) Currently translated at 16.7% (396 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ml/ --- vector/src/main/res/values-ml/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vector/src/main/res/values-ml/strings.xml b/vector/src/main/res/values-ml/strings.xml index 867f5661c2..7be6c5865e 100644 --- a/vector/src/main/res/values-ml/strings.xml +++ b/vector/src/main/res/values-ml/strings.xml @@ -412,4 +412,8 @@ പുറത്തിറങ്ങുക പുറത്തിറങ്ങുക കീ ബാക്കപ്പ് ഉപയൊഗിക്കൂ + %1$s ഒരു സ്റ്റിക്കർ അയച്ചു. + നിങ്ങൾ ഒരു ചിത്രം അയച്ചു. + %1$s ഒരു ചിത്രം അയച്ചു. + %1$s: %2$s \ No newline at end of file From 9e7d4c901e040943c81ed12ea9260eb3ba760abb Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 11 Mar 2021 16:47:21 +0300 Subject: [PATCH 076/249] Api interceptor implementation to allow app developers to peek responses. --- .../java/org/matrix/android/sdk/api/Matrix.kt | 7 + .../android/sdk/api/network/ApiInterceptor.kt | 90 +++++++++ .../matrix/android/sdk/api/network/ApiPath.kt | 183 ++++++++++++++++++ .../android/sdk/internal/di/NetworkModule.kt | 12 +- 4 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiInterceptor.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiPath.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt index a5d457222f..90bfbe3de6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt @@ -25,6 +25,8 @@ import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.legacy.LegacySessionImporter +import org.matrix.android.sdk.api.network.ApiInterceptor +import org.matrix.android.sdk.api.network.ApiPath import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.di.DaggerMatrixComponent @@ -49,6 +51,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo @Inject internal lateinit var olmManager: OlmManager @Inject internal lateinit var sessionManager: SessionManager @Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService + @Inject internal lateinit var apiInterceptor: ApiInterceptor init { Monarchy.init(context) @@ -73,6 +76,10 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo return legacySessionImporter } + fun registerApiInterceptorListener(path: ApiPath, listener: ApiInterceptor.Listener) { + apiInterceptor.addListener(path, listener) + } + companion object { private lateinit var instance: Matrix diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiInterceptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiInterceptor.kt new file mode 100644 index 0000000000..2b259a9797 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiInterceptor.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2020 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.network + +import okhttp3.Interceptor +import okhttp3.Response +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Interceptor class for provided api paths. + */ +@Singleton +class ApiInterceptor @Inject constructor() : Interceptor { + + interface Listener { + fun onApiResponse(path: ApiPath, response: String) + } + + init { + Timber.d("ApiInterceptor.init") + } + + private val apiResponseListenersMap = mutableMapOf>() + + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + val path = request.url.encodedPath.replaceFirst("/", "") + val method = request.method + + val response = chain.proceed(request) + + findApiPath(path, method)?.let { apiPath -> + response.peekBody(Long.MAX_VALUE).string().let { + apiResponseListenersMap[apiPath]?.forEach { listener -> + listener.onApiResponse(apiPath, it) + } + } + } + + return response + } + + private fun findApiPath(path: String, method: String): ApiPath? { + return apiResponseListenersMap + .keys + .find { apiPath -> + apiPath.method === method && isTheSamePath(apiPath.path, path) + } + } + + private fun isTheSamePath(pattern: String, path: String): Boolean { + val patternSegments = pattern.split("/") + val pathSegments = path.split("/") + + if (patternSegments.size != pathSegments.size) return false + + for (i in patternSegments.indices) { + if (patternSegments[i] != pathSegments[i] && !patternSegments[i].startsWith("{")) { + return false + } + } + return true + } + + /** + * Adds listener to send intercepted api responses through. + */ + fun addListener(path: ApiPath, listener: Listener) { + if (!apiResponseListenersMap.contains(path)) { + apiResponseListenersMap[path] = mutableListOf() + } + apiResponseListenersMap[path]?.add(listener) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiPath.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiPath.kt new file mode 100644 index 0000000000..4de8d49cf4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiPath.kt @@ -0,0 +1,183 @@ +/* + * 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 org.matrix.android.sdk.api.network + +import org.matrix.android.sdk.internal.network.NetworkConstants + +enum class ApiPath(val path: String, val method: String) { + // AuthApi + VERSIONS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "versions", "GET"), + REGISTER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register", "POST"), + ADD_3PID(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register/{threePid}/requestToken", "POST"), + LOGIN_FLOWS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login", "GET"), + LOGIN(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login", "POST"), + RESET_PASSWORD(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password/email/requestToken", "POST"), + RESET_PASSWORD_MAIL_CONFIRMED(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password", "POST"), + + // DirectoryApi + ROOM_ID_BY_ALIAS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}", "GET"), + ROOM_DIRECTORY_VISIBILITY(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/list/room/{roomId}", "GET"), + SET_ROOM_DIRECTORY_VISIBILITY(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/list/room/{roomId}", "PUT"), + ADD_ROOM_ALIAS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}", "PUT"), + DELETE_ROOM_ALIAS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}", "DELETE"), + + // CryptoApi + GET_DEVICES(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices", "GET"), + GET_DEVICE_INFO(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{deviceId}", "GET"), + UPLOAD_KEYS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/upload", "POST"), + DOWNLOAD_KEYS_FOR_USERS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/query", "POST"), + UPLOAD_SIGNING_KEYS(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "keys/device_signing/upload", "POST"), + UPLOAD_SIGNATURES(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "keys/signatures/upload", "POST"), + CLAIM_ONE_TIME_KEYS_FOR_USERS_DEVICES(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/claim", "POST"), + SEND_TO_DEVICE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sendToDevice/{eventType}/{txnId}", "PUT"), + DELETE_DEVICE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{device_id}", "DELETE"), + UPDATE_DEVICE_INFO(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{device_id}", "PUT"), + GET_KEY_CHANGES(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/changes", "GET"), + + // RoomKeysApi + CREATE_KEYS_BACKUP_VERSION(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version", "POST"), + GET_KEYS_BACKUP_LAST_VERSION(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version", "GET"), + GET_KEYS_BACKUP_VERSION(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}", "GET"), + UPDATE_KEYS_BACKUP_VERSION(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}", "PUT"), + STORE_ROOM_SESSION_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}", "PUT"), + STORE_ROOM_SESSIONS_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}", "PUT"), + STORE_SESSIONS_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys", "PUT"), + GET_ROOM_SESSION_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}", "GET"), + GET_ROOM_SESSIONS_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}", "GET"), + GET_SESSIONS_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys", "GET"), + DELETE_ROOM_SESSION_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}", "DELETE"), + DELETE_ROOM_SESSIONS_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}", "DELETE"), + DELETE_SESSIONS_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys", "DELETE"), + DELETE_BACKUP(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}", "DELETE"), + + // AccountApi + CHANGE_PASSWORD(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password", "POST"), + DEACTIVATE_ACCOUNT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/deactivate", "POST"), + + // SearchApi + SEARCH(NetworkConstants.URI_API_PREFIX_PATH_R0 + "search", "POST"), + + // FederationApi + GET_FEDERATION_VERSION(NetworkConstants.URI_FEDERATION_PATH + "version", "GET"), + + // VoipApi + GET_TURN_SERVER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "voip/turnServer", "GET"), + + // PushGatewayApi + NOTIFY_PUSH_GATEWAY(NetworkConstants.URI_PUSH_GATEWAY_PREFIX_PATH + "notify", "POST"), + + // GroupApi + GET_GROUP_SUMMARY(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/summary", "GET"), + GET_GROUP_ROOMS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/rooms", "GET"), + GET_GROUP_USERS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/users", "GET"), + + // CapabilitiesApi + GET_CAPABILITIES(NetworkConstants.URI_API_PREFIX_PATH_R0 + "capabilities", "GET"), + GET_VERSIONS(NetworkConstants.URI_API_PREFIX_PATH_ + "versions", "GET"), + PING(NetworkConstants.URI_API_PREFIX_PATH_ + "versions", "GET"), + + // IdentityApi + GET_ACCOUNT(NetworkConstants.URI_IDENTITY_PATH_V2 + "account", "GET"), + LOGOUT(NetworkConstants.URI_IDENTITY_PATH_V2 + "account/logout", "POST"), + IDENTITY_HAS_DETAILS(NetworkConstants.URI_IDENTITY_PATH_V2 + "hash_details", "GET"), + LOOKUP(NetworkConstants.URI_IDENTITY_PATH_V2 + "lookup", "POST"), + REQUEST_TOKEN_TO_BIND_EMAIL(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/email/requestToken", "POST"), + REQUEST_TOKEN_TO_BIND_MSISDN(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/msisdn/requestToken", "POST"), + SUBMIT_TOKEN(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/{medium}/submitToken", "POST"), + + // FilterApi + UPLOAD_FILTER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter", "POST"), + GET_FILTER_BY_ID(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter/{filterId}", "GET"), + + // IndentityAuthApi + IDENTITY_REGISTER(NetworkConstants.URI_IDENTITY_PATH_V2 + "account/register", "POST"), + + // MediaApi + GET_MEDIA_CONFIG(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "config", "GET"), + GET_PREVIEW_URL_DATA(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "preview_url", "GET"), + + // OpenIdApi + OPEN_ID_TOKEN(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/openid/request_token", "POST"), + + // ProfileApi + GET_PROFILE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}", "GET"), + GET_THREE_PIDS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid", "GET"), + SET_DISPLAY_NAME(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}/displayname", "PUT"), + SET_AVATAR_URL(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}/avatar_url", "PUT"), + BIND_THREE_PID(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/bind", "POST"), + UNBIND_THREE_PID(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/unbind", "POST"), + ADD_EMAIL(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/email/requestToken", "POST"), + ADD_MSISDN(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/msisdn/requestToken", "POST"), + FINALIZE_ADD_THREE_PID(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/add", "POST"), + DELETE_THREE_PID(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/delete", "POST"), + + // PusherRulesApi + GET_ALL_PUSHER_RULES(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/", "GET"), + UPDATE_ENABLE_PUSH_RULE_STATUS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/enabled", "PUT"), + UPDATE_PUSH_RULE_ACTIONS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/actions", "PUT"), + DELETE_PUSH_RULE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}", "DELETE"), + ADD_PUSH_RULE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}", "PUT"), + + // PusherApi + GET_PUSHERS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushers", "GET"), + SET_PUSHER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushers/set", "POST"), + + // SignOutApi + LOGIN_AGAIN(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login", "POST"), + SIGN_OUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "logout", "POST"), + + // RoomApi + GET_PUBLIC_ROOMS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "publicRooms", "POST"), + CREATE_ROOM(NetworkConstants.URI_API_PREFIX_PATH_R0 + "createRoom", "POST"), + GET_ROOM_MESSAGES_FROM(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/messages", "GET"), + GET_MEMBERS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/members", "GET"), + SEND_EVENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/send/{eventType}/{txId}", "PUT"), + GET_CONTEXT_OF_EVENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/context/{eventId}", "GET"), + GET_EVENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/event/{eventId}", "GET"), + SEND_READ_MARKER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/read_markers", "POST"), + INVITE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite", "POST"), + INVITE_USING_THREE_PID(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite", "POST"), + SEND_STATE_EVENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}", "PUT"), + SEND_STATE_EVENT_WITH_STATE_KEY(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}/{state_key}", "PUT"), + GET_ROOM_STATE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state", "GET"), + SEND_RELATION(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/send_relation/{parent_id}/{relation_type}/{event_type}", "POST"), + GET_RELATIONS(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/relations/{eventId}/{relationType}/{eventType}", "GET"), + JOIN_ROOM(NetworkConstants.URI_API_PREFIX_PATH_R0 + "join/{roomIdOrAlias}", "POST"), + LEAVE_ROOM(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/leave", "POST"), + BAN_USER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/ban", "POST"), + UNBAN_USER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/unban", "POST"), + KICK_USER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/kick", "POST"), + REDACT_EVENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/redact/{eventId}/{txnId}", "PUT"), + REPORT_CONTENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/report/{eventId}", "POST"), + GET_ALIASES(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2432/rooms/{roomId}/aliases", "GET"), + SEND_TYPING_STATE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/typing/{userId}", "PUT"), + PUT_TAG(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}", "PUT"), + DELETE_TAG(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}", "DELETE"), + + // SyncApi + SYNC(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sync", "GET"), + + // ThirdPartyApi + THIRD_PARTY_PROTOCOLS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/protocols", "GET"), + THIRD_PARTY_USER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/protocols/user/{protocol}", "GET"), + + // SearchUserApi + SEARCH_USERS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user_directory/search", "POST"), + + // AccountDataApi + SET_ACCOUNT_DATA(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/account_data/{type}", "PUT") +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt index f4688411ff..da50709de0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt @@ -28,6 +28,7 @@ import org.matrix.android.sdk.internal.network.interceptors.CurlLoggingIntercept import org.matrix.android.sdk.internal.network.interceptors.FormattedJsonHttpLogger import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor +import org.matrix.android.sdk.api.network.ApiInterceptor import java.util.concurrent.TimeUnit @Module @@ -54,6 +55,13 @@ internal object NetworkModule { return CurlLoggingInterceptor() } + @MatrixScope + @Provides + @JvmStatic + fun providesApiInterceptor(): ApiInterceptor { + return ApiInterceptor() + } + @MatrixScope @Provides @JvmStatic @@ -63,7 +71,8 @@ internal object NetworkModule { timeoutInterceptor: TimeOutInterceptor, userAgentInterceptor: UserAgentInterceptor, httpLoggingInterceptor: HttpLoggingInterceptor, - curlLoggingInterceptor: CurlLoggingInterceptor): OkHttpClient { + curlLoggingInterceptor: CurlLoggingInterceptor, + apiInterceptor: ApiInterceptor): OkHttpClient { return OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) @@ -76,6 +85,7 @@ internal object NetworkModule { .addInterceptor(timeoutInterceptor) .addInterceptor(userAgentInterceptor) .addInterceptor(httpLoggingInterceptor) + .addInterceptor(apiInterceptor) .apply { if (BuildConfig.LOG_PRIVATE_DATA) { addInterceptor(curlLoggingInterceptor) From 7db1d81eb697a4602922e09800767715e9967019 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 11 Mar 2021 16:51:17 +0300 Subject: [PATCH 077/249] Changelog added. --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 55c7387379..ce3281d987 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ Improvements 🙌: - Crypto improvement | Bulk send NO_OLM withheld code - Display the room shield in all room setting screens - Improve message with Emoji only detection (#3017) + - Api interceptor to allow app developers peek responses (#2986) Bugfix 🐛: - Fix bad theme change for the MainActivity From 21cff9a7493070cc68701111f878cc4bea1ac0e0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 22 Mar 2021 16:36:44 +0100 Subject: [PATCH 078/249] Update after Ganfra's review, and kotlinification --- .../java/org/matrix/android/sdk/api/Matrix.kt | 5 ++- .../sdk/api/network/ApiInterceptorListener.kt | 21 ++++++++++ .../matrix/android/sdk/api/network/ApiPath.kt | 2 +- .../android/sdk/internal/di/NetworkModule.kt | 9 +---- .../network/ApiInterceptor.kt | 40 +++++++++---------- 5 files changed, 44 insertions(+), 33 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiInterceptorListener.kt rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{api => internal}/network/ApiInterceptor.kt (66%) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt index 90bfbe3de6..8a27f364d2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt @@ -25,11 +25,12 @@ import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.legacy.LegacySessionImporter -import org.matrix.android.sdk.api.network.ApiInterceptor +import org.matrix.android.sdk.api.network.ApiInterceptorListener import org.matrix.android.sdk.api.network.ApiPath import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.di.DaggerMatrixComponent +import org.matrix.android.sdk.internal.network.ApiInterceptor import org.matrix.android.sdk.internal.network.UserAgentHolder import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver import org.matrix.olm.OlmManager @@ -76,7 +77,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo return legacySessionImporter } - fun registerApiInterceptorListener(path: ApiPath, listener: ApiInterceptor.Listener) { + fun registerApiInterceptorListener(path: ApiPath, listener: ApiInterceptorListener) { apiInterceptor.addListener(path, listener) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiInterceptorListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiInterceptorListener.kt new file mode 100644 index 0000000000..ad21da8fdf --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiInterceptorListener.kt @@ -0,0 +1,21 @@ +/* + * 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.network + +interface ApiInterceptorListener { + fun onApiResponse(path: ApiPath, response: String) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiPath.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiPath.kt index 4de8d49cf4..db112a30b2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiPath.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiPath.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt index da50709de0..0d0892b608 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt @@ -28,7 +28,7 @@ import org.matrix.android.sdk.internal.network.interceptors.CurlLoggingIntercept import org.matrix.android.sdk.internal.network.interceptors.FormattedJsonHttpLogger import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor -import org.matrix.android.sdk.api.network.ApiInterceptor +import org.matrix.android.sdk.internal.network.ApiInterceptor import java.util.concurrent.TimeUnit @Module @@ -55,13 +55,6 @@ internal object NetworkModule { return CurlLoggingInterceptor() } - @MatrixScope - @Provides - @JvmStatic - fun providesApiInterceptor(): ApiInterceptor { - return ApiInterceptor() - } - @MatrixScope @Provides @JvmStatic diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiInterceptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt similarity index 66% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiInterceptor.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt index 2b259a9797..8b188910a1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiInterceptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * 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. @@ -14,29 +14,28 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.network +package org.matrix.android.sdk.internal.network import okhttp3.Interceptor import okhttp3.Response +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.network.ApiInterceptorListener +import org.matrix.android.sdk.api.network.ApiPath +import org.matrix.android.sdk.internal.di.MatrixScope import timber.log.Timber import javax.inject.Inject -import javax.inject.Singleton /** * Interceptor class for provided api paths. */ -@Singleton -class ApiInterceptor @Inject constructor() : Interceptor { - - interface Listener { - fun onApiResponse(path: ApiPath, response: String) - } +@MatrixScope +internal class ApiInterceptor @Inject constructor() : Interceptor { init { Timber.d("ApiInterceptor.init") } - private val apiResponseListenersMap = mutableMapOf>() + private val apiResponseListenersMap = mutableMapOf>() override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() @@ -46,9 +45,11 @@ class ApiInterceptor @Inject constructor() : Interceptor { val response = chain.proceed(request) findApiPath(path, method)?.let { apiPath -> - response.peekBody(Long.MAX_VALUE).string().let { + response.peekBody(Long.MAX_VALUE).string().let { networkResponse -> apiResponseListenersMap[apiPath]?.forEach { listener -> - listener.onApiResponse(apiPath, it) + tryOrNull("Error in the implementation") { + listener.onApiResponse(apiPath, networkResponse) + } } } } @@ -70,21 +71,16 @@ class ApiInterceptor @Inject constructor() : Interceptor { if (patternSegments.size != pathSegments.size) return false - for (i in patternSegments.indices) { - if (patternSegments[i] != pathSegments[i] && !patternSegments[i].startsWith("{")) { - return false - } + return patternSegments.indices.all { i -> + patternSegments[i] == pathSegments[i] || patternSegments[i].startsWith("{") } - return true } /** * Adds listener to send intercepted api responses through. */ - fun addListener(path: ApiPath, listener: Listener) { - if (!apiResponseListenersMap.contains(path)) { - apiResponseListenersMap[path] = mutableListOf() - } - apiResponseListenersMap[path]?.add(listener) + fun addListener(path: ApiPath, listener: ApiInterceptorListener) { + apiResponseListenersMap.getOrPut(path) { mutableListOf() } + .add(listener) } } From f6e43a5305b9b2476025d136400d94a1e9d4a7cc Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 22 Mar 2021 17:13:16 +0100 Subject: [PATCH 079/249] Add Android test on ApiInterceptor --- matrix-sdk-android/build.gradle | 1 - .../java/org/matrix/android/sdk/api/Matrix.kt | 8 +++ .../sdk/api/network/ApiInterceptorTest.kt | 53 +++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 5ca5ce1e05..7fb626ab46 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -185,7 +185,6 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'org.amshove.kluent:kluent-android:1.61' - // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 androidTestImplementation 'io.mockk:mockk-android:1.11.0' androidTestImplementation "androidx.arch.core:core-testing:$arch_version" androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt index 03943cea14..11251b8a5d 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt @@ -27,9 +27,12 @@ import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.legacy.LegacySessionImporter +import org.matrix.android.sdk.api.network.ApiInterceptorListener +import org.matrix.android.sdk.api.network.ApiPath import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.common.DaggerTestMatrixComponent import org.matrix.android.sdk.internal.SessionManager +import org.matrix.android.sdk.internal.network.ApiInterceptor import org.matrix.android.sdk.internal.network.UserAgentHolder import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver import org.matrix.olm.OlmManager @@ -51,6 +54,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo @Inject internal lateinit var olmManager: OlmManager @Inject internal lateinit var sessionManager: SessionManager @Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService + @Inject internal lateinit var apiInterceptor: ApiInterceptor private val uiHandler = Handler(Looper.getMainLooper()) @@ -79,6 +83,10 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo return legacySessionImporter } + fun registerApiInterceptorListener(path: ApiPath, listener: ApiInterceptorListener) { + apiInterceptor.addListener(path, listener) + } + companion object { private lateinit var instance: Matrix diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt new file mode 100644 index 0000000000..93fdccfd48 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt @@ -0,0 +1,53 @@ +/* + * 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 org.matrix.android.sdk.api.network + +import org.amshove.kluent.shouldBeEqualTo +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters +import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.SessionTestParams +import org.matrix.android.sdk.common.TestConstants +import timber.log.Timber + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +class ApiInterceptorTest : InstrumentedTest { + + private val commonTestHelper = CommonTestHelper(context()) + + @Test + fun createAccountTest() { + var counter = 0 + commonTestHelper.matrix.registerApiInterceptorListener(ApiPath.REGISTER, object : ApiInterceptorListener { + override fun onApiResponse(path: ApiPath, response: String) { + Timber.w("onApiResponse($path): $response") + counter++ + } + }) + + val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) + + commonTestHelper.signOutAndClose(session) + + counter shouldBeEqualTo 2 + } +} From d85d44bf4b2f44c05b21f827686ed10da61f1d63 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 22 Mar 2021 18:09:42 +0100 Subject: [PATCH 080/249] Add ApiInterceptor.removeListener() --- .../org/matrix/android/sdk/InstrumentedTest.kt | 5 ----- .../java/org/matrix/android/sdk/api/Matrix.kt | 4 ++++ .../sdk/api/network/ApiInterceptorTest.kt | 17 +++++++++++------ .../java/org/matrix/android/sdk/api/Matrix.kt | 4 ++++ .../sdk/internal/network/ApiInterceptor.kt | 18 ++++++++++++++++-- 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt index b784884363..583406346e 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt @@ -20,7 +20,6 @@ import android.content.Context import androidx.test.core.app.ApplicationProvider import org.matrix.android.sdk.test.shared.createTimberTestRule import org.junit.Rule -import java.io.File interface InstrumentedTest { @@ -30,8 +29,4 @@ interface InstrumentedTest { fun context(): Context { return ApplicationProvider.getApplicationContext() } - - fun cacheDir(): File { - return context().cacheDir - } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt index 11251b8a5d..c439da8407 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt @@ -87,6 +87,10 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo apiInterceptor.addListener(path, listener) } + fun unregisterApiInterceptorListener(path: ApiPath, listener: ApiInterceptorListener) { + apiInterceptor.removeListener(path, listener) + } + companion object { private lateinit var instance: Matrix diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt index 93fdccfd48..149acbc18a 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt @@ -35,19 +35,24 @@ class ApiInterceptorTest : InstrumentedTest { private val commonTestHelper = CommonTestHelper(context()) @Test - fun createAccountTest() { - var counter = 0 - commonTestHelper.matrix.registerApiInterceptorListener(ApiPath.REGISTER, object : ApiInterceptorListener { + fun apiInterceptorTest() { + val responses = mutableListOf() + + val listener = object : ApiInterceptorListener { override fun onApiResponse(path: ApiPath, response: String) { Timber.w("onApiResponse($path): $response") - counter++ + responses.add(response) } - }) + } + + commonTestHelper.matrix.registerApiInterceptorListener(ApiPath.REGISTER, listener) val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) commonTestHelper.signOutAndClose(session) - counter shouldBeEqualTo 2 + commonTestHelper.matrix.unregisterApiInterceptorListener(ApiPath.REGISTER, listener) + + responses.size shouldBeEqualTo 2 } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt index 8a27f364d2..9980259266 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt @@ -81,6 +81,10 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo apiInterceptor.addListener(path, listener) } + fun unregisterApiInterceptorListener(path: ApiPath, listener: ApiInterceptorListener) { + apiInterceptor.removeListener(path, listener) + } + companion object { private lateinit var instance: Matrix diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt index 8b188910a1..5d73264d11 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt @@ -80,7 +80,21 @@ internal class ApiInterceptor @Inject constructor() : Interceptor { * Adds listener to send intercepted api responses through. */ fun addListener(path: ApiPath, listener: ApiInterceptorListener) { - apiResponseListenersMap.getOrPut(path) { mutableListOf() } - .add(listener) + synchronized(apiResponseListenersMap) { + apiResponseListenersMap.getOrPut(path) { mutableListOf() } + .add(listener) + } + } + + /** + * Remove listener to send intercepted api responses through. + */ + fun removeListener(path: ApiPath, listener: ApiInterceptorListener) { + synchronized(apiResponseListenersMap) { + apiResponseListenersMap[path]?.remove(listener) + if (apiResponseListenersMap[path]?.isEmpty() == true) { + apiResponseListenersMap.remove(path) + } + } } } From f106176752e54d806b6abdcf18f9346c61f982f0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 22 Mar 2021 18:26:29 +0100 Subject: [PATCH 081/249] Add missing synchronized --- .../android/sdk/internal/network/ApiInterceptor.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt index 5d73264d11..afd3347283 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt @@ -44,11 +44,13 @@ internal class ApiInterceptor @Inject constructor() : Interceptor { val response = chain.proceed(request) - findApiPath(path, method)?.let { apiPath -> - response.peekBody(Long.MAX_VALUE).string().let { networkResponse -> - apiResponseListenersMap[apiPath]?.forEach { listener -> - tryOrNull("Error in the implementation") { - listener.onApiResponse(apiPath, networkResponse) + synchronized(apiResponseListenersMap) { + findApiPath(path, method)?.let { apiPath -> + response.peekBody(Long.MAX_VALUE).string().let { networkResponse -> + apiResponseListenersMap[apiPath]?.forEach { listener -> + tryOrNull("Error in the implementation") { + listener.onApiResponse(apiPath, networkResponse) + } } } } From 5cd86c685d42d8311a5409483085d5d45d321a4d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 22 Mar 2021 18:29:06 +0100 Subject: [PATCH 082/249] Copyright --- .../org/matrix/android/sdk/api/network/ApiInterceptorTest.kt | 2 +- .../org/matrix/android/sdk/internal/network/ApiInterceptor.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt index 149acbc18a..9371154aaf 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 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. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt index afd3347283..1dd6e75ea5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 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. From 5136979352ef32f4c22c600fb09cfed036477024 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 22 Mar 2021 18:33:06 +0100 Subject: [PATCH 083/249] Cleanup on unbinding Valere's review --- .../core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt index 2f7c22e4b4..5ff7a07e3c 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt @@ -77,6 +77,11 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel(R.id.bottom_sheet_message_preview_avatar) val sender by bind(R.id.bottom_sheet_message_preview_sender) From 274aef8f248f3079934de1d52e74b3f4c2b54000 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 22 Mar 2021 19:15:06 +0100 Subject: [PATCH 084/249] Code qual --- tools/check/forbidden_strings_in_code.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt index 468fe717c0..7b7c44b9fd 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===92 +enum class===93 ### Do not import temporary legacy classes import org.matrix.android.sdk.internal.legacy.riot===3 From 024dcf1f6ce9c953fc3a243e7dc56eb1e371b042 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 23 Mar 2021 21:09:52 +0100 Subject: [PATCH 085/249] Be robust to Exception. And display details about it in the bottom sheet --- .../action/MessageActionsViewModel.kt | 72 ++++++++++--------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index ac1c2258aa..b5d3102f46 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -24,6 +24,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.R +import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.extensions.canReact import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel @@ -65,6 +66,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted private val htmlCompressor: VectorHtmlCompressor, private val session: Session, private val noticeEventFormatter: NoticeEventFormatter, + private val errorFormatter: ErrorFormatter, private val stringProvider: StringProvider, private val pillsPostProcessorFactory: PillsPostProcessor.Factory, private val vectorPreferences: VectorPreferences @@ -171,42 +173,46 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } private fun computeMessageBody(timelineEvent: TimelineEvent): CharSequence { - if (timelineEvent.root.isRedacted()) { - return noticeEventFormatter.formatRedactedEvent(timelineEvent.root) - } + return try { + if (timelineEvent.root.isRedacted()) { + noticeEventFormatter.formatRedactedEvent(timelineEvent.root) + } else { + when (timelineEvent.root.getClearType()) { + EventType.MESSAGE, + EventType.STICKER -> { + val messageContent: MessageContent? = timelineEvent.getLastMessageContent() + if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) { + val html = messageContent.formattedBody + ?.takeIf { it.isNotBlank() } + ?.let { htmlCompressor.compress(it) } + ?: messageContent.body - return when (timelineEvent.root.getClearType()) { - EventType.MESSAGE, - EventType.STICKER -> { - val messageContent: MessageContent? = timelineEvent.getLastMessageContent() - if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) { - val html = messageContent.formattedBody - ?.takeIf { it.isNotBlank() } - ?.let { htmlCompressor.compress(it) } - ?: messageContent.body - - eventHtmlRenderer.get().render(html, pillsPostProcessor) - } else if (messageContent is MessageVerificationRequestContent) { - stringProvider.getString(R.string.verification_request) - } else { - messageContent?.body + eventHtmlRenderer.get().render(html, pillsPostProcessor) + } else if (messageContent is MessageVerificationRequestContent) { + stringProvider.getString(R.string.verification_request) + } else { + messageContent?.body + } + } + EventType.STATE_ROOM_NAME, + EventType.STATE_ROOM_TOPIC, + EventType.STATE_ROOM_AVATAR, + EventType.STATE_ROOM_MEMBER, + EventType.STATE_ROOM_ALIASES, + EventType.STATE_ROOM_CANONICAL_ALIAS, + EventType.STATE_ROOM_HISTORY_VISIBILITY, + EventType.STATE_ROOM_SERVER_ACL, + EventType.CALL_INVITE, + EventType.CALL_CANDIDATES, + EventType.CALL_HANGUP, + EventType.CALL_ANSWER -> { + noticeEventFormatter.format(timelineEvent) + } + else -> null } } - EventType.STATE_ROOM_NAME, - EventType.STATE_ROOM_TOPIC, - EventType.STATE_ROOM_AVATAR, - EventType.STATE_ROOM_MEMBER, - EventType.STATE_ROOM_ALIASES, - EventType.STATE_ROOM_CANONICAL_ALIAS, - EventType.STATE_ROOM_HISTORY_VISIBILITY, - EventType.STATE_ROOM_SERVER_ACL, - EventType.CALL_INVITE, - EventType.CALL_CANDIDATES, - EventType.CALL_HANGUP, - EventType.CALL_ANSWER -> { - noticeEventFormatter.format(timelineEvent) - } - else -> null + } catch (failure: Throwable) { + errorFormatter.toHumanReadable(failure) } ?: "" } From 276677295fd8d04099d3110cebf7a4ca1cdfb082 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 24 Mar 2021 08:49:57 +0000 Subject: [PATCH 086/249] =?UTF-8?q?Deleted=20translation=20using=20Weblate?= =?UTF-8?q?=20(Norwegian=20Bokm=C3=A5l=20(nb))?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vector/src/main/res/values-nb/strings.xml | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 vector/src/main/res/values-nb/strings.xml 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 From 30515492ca447a9229ddfe86d3f0881bbd5c8af8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Mar 2021 06:31:57 +0000 Subject: [PATCH 087/249] Bump recyclerview from 1.2.0-beta02 to 1.2.0-rc01 Bumps recyclerview from 1.2.0-beta02 to 1.2.0-rc01. Signed-off-by: dependabot[bot] --- attachment-viewer/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/attachment-viewer/build.gradle b/attachment-viewer/build.gradle index 7725bf23db..5a8cce92e8 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-beta02" + implementation "androidx.recyclerview:recyclerview:1.2.0-rc01" implementation 'com.google.android.material:material:1.3.0' } \ No newline at end of file diff --git a/vector/build.gradle b/vector/build.gradle index 70538b100a..7a87822849 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -320,7 +320,7 @@ 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-beta02" + implementation "androidx.recyclerview:recyclerview:1.2.0-rc01" implementation 'androidx.appcompat:appcompat:1.2.0' implementation "androidx.fragment:fragment-ktx:$fragment_version" implementation 'androidx.constraintlayout:constraintlayout:2.0.4' From ee1059f1dbc36b8a6e97c4e4d072dc925d008ffe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Mar 2021 06:33:50 +0000 Subject: [PATCH 088/249] Bump fragment-ktx from 1.3.0 to 1.3.2 Bumps fragment-ktx from 1.3.0 to 1.3.2. Signed-off-by: dependabot[bot] --- multipicker/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/multipicker/build.gradle b/multipicker/build.gradle index 89a692a782..26afd5fb77 100644 --- a/multipicker/build.gradle +++ b/multipicker/build.gradle @@ -43,7 +43,7 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.2.0' - implementation "androidx.fragment:fragment-ktx:1.3.0" + implementation "androidx.fragment:fragment-ktx:1.3.2" implementation 'androidx.exifinterface:exifinterface:1.3.2' // Log diff --git a/vector/build.gradle b/vector/build.gradle index 70538b100a..6ce769d35c 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -291,7 +291,7 @@ android { dependencies { def epoxy_version = '4.4.3' - def fragment_version = '1.3.0' + def fragment_version = '1.3.2' def arrow_version = "0.8.2" def markwon_version = '4.1.2' def big_image_viewer_version = '1.7.1' From 627e9a95437eaa9ea71040a33cc74bb90d657176 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 25 Mar 2021 11:18:26 +0100 Subject: [PATCH 089/249] Add version details on the login screen, in debug or developer mode --- CHANGES.md | 2 +- .../app/features/login/LoginSplashFragment.kt | 16 +++++++++++++++- .../main/res/layout/fragment_login_splash.xml | 11 +++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fdf65b9f1c..68b6194734 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -30,7 +30,7 @@ Test: - Other changes: - - + - Add version details on the login screen, in debug or developer mode Changes in Element 1.1.3 (2021-03-18) =================================================== diff --git a/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt b/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt index 03c87c5eec..956cf1615d 100644 --- a/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt +++ b/vector/src/main/java/im/vector/app/features/login/LoginSplashFragment.kt @@ -16,18 +16,24 @@ package im.vector.app.features.login +import android.annotation.SuppressLint import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.isVisible +import im.vector.app.BuildConfig import im.vector.app.databinding.FragmentLoginSplashBinding +import im.vector.app.features.settings.VectorPreferences import javax.inject.Inject /** * In this screen, the user is viewing an introduction to what he can do with this application */ -class LoginSplashFragment @Inject constructor() : AbstractLoginFragment() { +class LoginSplashFragment @Inject constructor( + private val vectorPreferences: VectorPreferences +) : AbstractLoginFragment() { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginSplashBinding { return FragmentLoginSplashBinding.inflate(inflater, container, false) @@ -41,6 +47,14 @@ class LoginSplashFragment @Inject constructor() : AbstractLoginFragment + + From 081a904f8a60b6254f6f3f39b7affc207922a797 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Mar 2021 12:57:42 +0000 Subject: [PATCH 090/249] Bump kotlin_version from 1.4.31 to 1.4.32 Bumps `kotlin_version` from 1.4.31 to 1.4.32. Updates `kotlin-gradle-plugin` from 1.4.31 to 1.4.32 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/commits) Updates `kotlin-stdlib-jdk7` from 1.4.31 to 1.4.32 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/commits) Updates `kotlin-stdlib` from 1.4.31 to 1.4.32 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/commits) Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 4e2c81a379..637b1de7cb 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { // Ref: https://kotlinlang.org/releases.html - ext.kotlin_version = '1.4.31' + ext.kotlin_version = '1.4.32' ext.kotlin_coroutines_version = "1.4.2" repositories { google() From edee97142faefd7e0019f12b19ba7790a1c63a11 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Thu, 25 Mar 2021 07:33:18 +0000 Subject: [PATCH 091/249] Translated using Weblate (Czech) Currently translated at 100.0% (2362 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- vector/src/main/res/values-cs/strings.xml | 36 +++++++++++------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index 341300cbf9..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í From da16160d282201d040ff3a84f1fc0ff0f8ef7c34 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 23 Mar 2021 19:45:00 +0000 Subject: [PATCH 092/249] Translated using Weblate (French) Currently translated at 100.0% (2362 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- vector/src/main/res/values-fr/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 57382fa9df..7c1496a73e 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -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 From 00c051d4ff73f54b42bbd18e88935bec2117e8be Mon Sep 17 00:00:00 2001 From: Thor Arne Johansen Date: Tue, 23 Mar 2021 12:15:03 +0000 Subject: [PATCH 093/249] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 61.9% (1463 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/nb_NO/ --- vector/src/main/res/values-nb-rNO/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-nb-rNO/strings.xml b/vector/src/main/res/values-nb-rNO/strings.xml index a4fd61011d..fc16bd5ca0 100644 --- a/vector/src/main/res/values-nb-rNO/strings.xml +++ b/vector/src/main/res/values-nb-rNO/strings.xml @@ -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. From f502b323e72340d9661098c2a16cce9920603ea6 Mon Sep 17 00:00:00 2001 From: Marcelo Filho Date: Tue, 23 Mar 2021 18:27:52 +0000 Subject: [PATCH 094/249] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2362 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index a2f930a272..b46bacebca 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -2701,4 +2701,44 @@ \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 From 98839e79ad7e10195eeefac9a5f8a9165ebcdbd7 Mon Sep 17 00:00:00 2001 From: ozzii Date: Mon, 22 Mar 2021 17:35:42 +0000 Subject: [PATCH 095/249] Translated using Weblate (Serbian) Currently translated at 26.3% (623 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sr/ --- vector/src/main/res/values-sr/strings.xml | 108 +++++++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-sr/strings.xml b/vector/src/main/res/values-sr/strings.xml index b44a7f78c5..5e56ffcad5 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 @@ -602,9 +602,113 @@ Не може се покренути позив, покушајте касније Конференцијски позив у току. \nПридружи се као %1$s или %2$s - Permalink + ТрајнаВеза Почетна синхронизација: \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 Вас је позвао да се придружите овој соби + Ићи на прву непрочитану поруку. + Синхронизација… + Отвори заглавље + Листа чланова + Одбаци + Преглед + Придружи се + Избриши + Настави \ No newline at end of file From f3996e8634d87a4faed737862d451c0ea167e9b0 Mon Sep 17 00:00:00 2001 From: GokdenizK Date: Wed, 24 Mar 2021 07:31:02 +0000 Subject: [PATCH 096/249] Translated using Weblate (Turkish) Currently translated at 67.9% (1605 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/tr/ --- vector/src/main/res/values-tr/strings.xml | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/vector/src/main/res/values-tr/strings.xml b/vector/src/main/res/values-tr/strings.xml index 6a065d06e8..aa234ac78c 100644 --- a/vector/src/main/res/values-tr/strings.xml +++ b/vector/src/main/res/values-tr/strings.xml @@ -1777,4 +1777,50 @@ %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 + Özel + %1$s widgetını kaldırdınız + %1$s widgetını eklediniz + Profilinizi güncellediniz + 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 From a1757c54083960a111c1f1fc90c3bde93e1fecd5 Mon Sep 17 00:00:00 2001 From: RainSlide Date: Thu, 25 Mar 2021 16:34:06 +0000 Subject: [PATCH 097/249] Translated using Weblate (Chinese (Simplified)) Currently translated at 97.3% (2300 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- vector/src/main/res/values-zh-rCN/strings.xml | 535 ++++++++++-------- 1 file changed, 310 insertions(+), 225 deletions(-) diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index 004e95019b..365b6827a9 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,14 +134,14 @@ 您接听了通话。 您结束了通话。 您已让未来的聊天室记录对 %1$s 可见 - 您开启了端到端加密(%1$s) + 您开启了端对端加密(%1$s) 您升级了此聊天室。 您请求了 VoIP 会议 您移除了聊天室名称 您移除了聊天室主题 %1$s 移除了聊天室头像 您移除了聊天室头像 - 您更新了您的个人档案 %1$s + 您更新了您的资料 %1$s 您向 %1$s 发送了加入聊天室的邀请 您已撤回了对 %1$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]:// 开头 @@ -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在设置中,您可以在个人资料里添加您的电子邮件。 用户名已被使用 无法注册:电子邮箱所有权验证失败 无法识别指定的访问令牌 @@ -588,8 +588,8 @@ 对不起。因为权限不足,操作已取消 保存至下载? 移除 - 此邀请已发送至未与此账户关联的 %s。 -\n您可能希望用一个不同的账户登录,或者把这个电子邮箱加入到你的账户。 + 此邀请已发送至未与此账号关联的 %s。 +\n您可能希望用一个不同的账号登录,或者把这个电子邮箱加入到你的账号。 这是此聊天室的预览。与聊天室的交互已禁用。 通话 您将不能撤销这个修改,因为您正在让这个用户和您拥有相同的特权级别。 @@ -652,8 +652,8 @@ \n你同意为此目的分享你的通讯录吗\? 空闲 仅 Matrix 用户 - 您的手机信任的证书已被更改。这非常反常。建议您不要接受此新证书。 - 证书已从以前受信任的更改为不受信任的证书。服务器可能已更新其证书。请联系管理员并核对服务器的指纹。 + 证书已从某个您的手机所信任的证书更改为另一个。这非常反常。建议您不要接受此新证书。 + 证书已从曾受信任的证书更改为不受信任的证书。服务器可能已更新其证书,请联系管理员并核对服务器的指纹。 请仅在服务器管理员已经发布了与上述指纹相匹配的指纹的情况下接受该证书。 成员 ID 的格式不正确。应该是一个电子邮箱地址或 Matrix ID,如 “@localpart:domain” @@ -671,13 +671,13 @@ 正在等待验证 代码 访问和可见性 - 聊天室访问 + 聊天室访问权限 实验室 端对端加密已激活 %s 已尝试在这个聊天室的时间线上加载一个特定的时间点,但无法找到它。 公开名称 为验证此设备是否可信,请通过其他方式(例如面对面交换或拨打电话)与其拥有者联系,并询问他们该设备的用户设置中的密钥是否与以下密钥匹配: - 如果它们不匹配,您通信的安全性可能会受到影响。 + 如果它们不匹配,您通讯的安全性可能会受到影响。 这个聊天室包含未经验证的未知设备。 \n这意味着无法保证该设备属于其声称的用户。 \n我们建议您在继续操作之前,先验证每个设备,但如果您愿意也可以不验证而重新发送消息。 @@ -841,8 +841,8 @@ %d 位成员 - 无效的社区 ID - “%s” 不是一个有效的社区 ID + 社区 ID 无效 + “%s” 不是有效的社区 ID %d 条未读消息 @@ -893,29 +893,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 +940,20 @@ 请输入您的密码。 发言 如果可能的话,请使用英文撰写问题描述。 - 发送加密的回复… + 发送加密回复… 发送回复(未加密)… 发送前预览媒体文件 使用回车键发送消息 显示动作 - 依照 ID 封禁用户 - 依照 ID 解禁用户 + 按照 ID 封禁用户 + 按照 ID 解禁用户 设置用户的权限等级 - 依照 ID 取消用户管理员权限 - 依照 ID 邀请用户进入当前聊天室 - 依照别名加入聊天室 + 按照 ID 取消用户管理员权限 + 按照 ID 邀请用户进入当前聊天室 + 按照别名加入聊天室 离开聊天室 设置聊天室主题 - 依照 ID 踢出用户 + 按照 ID 踢出用户 更改您显示的昵称 打开/关闭 markdown 修复 Matrix Apps 管理 @@ -1021,10 +1025,10 @@ 通知已在系统设置中禁用。 \n请检查系统设置。 打开设置 - 帐号设置。 - 您的帐号已启用通知。 - 您的账户已禁用通知。 -\n请检查账户设置。 + 账号设置。 + 您的账号已启用通知。 + 您的账号已禁用通知。 +\n请检查账号设置。 启用 设备设置。 已为此设备启用通知。 @@ -1056,7 +1060,7 @@ 启用开机时启动 检查后台限制 电池优化 - 若主服务器支持本功能,在聊天时预览链接内容。 + 若主服务器支持此功能,在聊天中预览链接内容。 发送正在输入通知 让聊天室中的其他用户知道您正在输入。 Markdown 格式化 @@ -1065,10 +1069,10 @@ 点击已阅回执以显示所有已经阅读过某条消息的用户。 显示加入与离开事件 邀请、移除与封禁事件不受影响。 - 显示账户变动事件 + 显示账号变动事件 包括头像与显示名称的变动。 后台连接 - ${app_name}需要保持低影响的后台连接,以便获得可靠的通知。 + ${app_name} 需要保持低影响的后台连接,以便获得可靠的通知。 \n下一个屏幕中,系统将提示您允许 ${app_name} 始终在后台运行,请点击“允许“。 授予权限 在验证您的电子邮件地址时发生了一个错误。 @@ -1089,8 +1093,8 @@ 您的主服务器尚未支持延迟加载聊天室成员,请稍候再试。 通过仅载入最近聊天中出现的聊天室成员来提升性能。 延迟加载聊天室成员 - 已停用 Markdown。 - 已启用 Markdown。 + Markdown 已禁用。 + Markdown 已启用。 视频通话中… 自动重启通知服务 服务被停止,并已自动重启。 @@ -1106,23 +1110,23 @@ ${app_name} 未被电池优化影响。 如果设备在未充电的情况下关屏静置一段时间,其将进入打盹模式(Doze)。这将阻止应用访问网络并延后其运行、同步、与响铃。 忽略电池优化 - 请输入用于加密密钥备份的密码。您在恢复此备份时需要使用此密码。 - 创建密码 - 密码必须对应 + 请输入用于加密被导出密钥的密语。恢复此备份时,必须输入相同的密语才能导入密钥。 + 创建密语 + 密语必须对应 指令 %s 需要更多参数,或者有些参数不正确。 没有可用的 Google Play Services APK。消息通知可能不能正常工作。 密钥备份 使用备份密钥 密钥备份尚未完成,请等待… - 如果您此时登出账号,您将会失去您的已加密信息 - 密钥备份进行中。如果您此时登出账号将无法再访问您的已加密信息。 - 您的所有设备都应当启用安全密钥备份以确保您不会失去您的已加密信息的访问权。 - 我不想要我的已加密信息 - 密钥备份中… + 如果您此时登出账号,您将会失去您的已加密消息 + 密钥备份进行中。如果您此时登出账号将无法再访问您的已加密消息。 + 您的所有设备都应当启用安全密钥备份以确保您不会失去您的已加密消息的访问权。 + 我不想要我的已加密消息 + 正在备份密钥… 使用备份密钥 确定吗? 备份 - 如果您在登出账号之前不备份密钥,您将失去您的已加密信息的访问权。 + 如果您在登出账号之前不备份密钥,您将失去您的已加密消息的访问权。 留下 跳过 完成 @@ -1148,34 +1152,34 @@ 选择指示灯颜色,震动,铃声… 加密密钥管理 省流量模式使用了特定的过滤器,所以状态更新和输入状态通知将会被过滤掉。 - 已加密信息恢复 + 恢复已加密消息 管理密钥备份 静音 请输入一个用户名。 - 请输入密码 - 密码太弱 - 如果您想要 ${app_name} 生成一个恢复密钥,请删除密码。 + 请输入密语 + 密语太弱了 + 如果您想要 ${app_name} 生成一个恢复密钥,请删除密语。 没有可用的 Matrix 会话 - 已加密信息永不丢失 + 永不丢失已加密消息 加密聊天室中的信息会被端对端加密以确保安全。只有您和拥有密钥的接收方可以读取这些信息。 \n \n安全地备份您的密钥以免丢失信息。 开始使用备份密钥 (高级) 手动导出密钥 - 使用密码以保护您的备份。 - 我们将会在主服务器上保存一份您的密钥的加密拷贝。设置一个密码来保护您的备份的安全。 + 使用密语保护您的备份。 + 我们将会在主服务器上为您的密钥保存一份加密拷贝。设置一个密语来保护您的备份的安全。 \n -\n为了最大的安全性,这个密码应当与您的账号密码不同。 - 设置密码 - 备份创建中 +\n为了最大的安全性,此密语应当与您的账号密码不同。 + 设置密语 + 正在创建备份 或者用一个恢复密钥来保护您的备份,将其保存到另一个安全的地方。 (高级)设置一个恢复密钥 成功! 正在备份您的密钥。 - 您的恢复密钥是一张安全网 - 如果您忘记了密码您可以利用它重获您的已加密信息的访问权。 -\n请将您的恢复密钥保存在一个非常安全的地方,比如密码管理器中 (或保险箱里) - 将您的恢复密钥保存在一个非常安全的地方,比如密码管理器中 (或保险箱里) + 您的恢复密钥是一张安全网——如果您忘记了密语,您可以利用它重获您的已加密消息的访问权。 +\n请将您的恢复密钥保存在一个非常安全的地方,比如密码管理器中(或保险箱里) + 将您的恢复密钥保存在一个非常安全的地方,比如密码管理器中(或保险箱里) 完成 我已经制作了一份拷贝 保存恢复密钥 @@ -1185,8 +1189,8 @@ \n \n警告:如果应用被卸载,此文件可能会被删除。 请制作一份拷贝 - 分享恢复密钥 … - 用密码来生成恢复密钥,此过程可能会花费几秒钟。 + 分享恢复密钥… + 正在使用密语来生成恢复密钥,此过程可能会花费几秒钟。 恢复密钥 意外错误 备份开始 @@ -1194,19 +1198,19 @@ 您确定吗? 如果您登出账号或者丢失此设备,您可能再也无法访问您的信息。 正在获取备份的版本 … - 使用恢复密码解锁您的已加密历史消息 + 使用恢复密语解锁您的已加密历史消息 使用您的恢复密钥 - 不知道您的恢复密码,您可以 %s 。 + 如果不知道您的恢复密语,您可以 %s。 使用恢复密钥解锁您的已加密历史消息 输入恢复密钥 - 信息恢复 + 消息恢复 丢失了恢复密钥?您可以在设置中新建一个。 - 无法使用此密码解密备份:请检查您输入的恢复密码是否正确。 + 无法使用此密语解密备份:请检查您输入的恢复密语是否正确。 网络错误:请检查您的网络连接并重试。 - 备份恢复中: - 恢复密钥计算中 … - 密钥下载中 … - 密钥导入中 … + 正在恢复备份: + 正在计算恢复密钥… + 正在下载密钥… + 正在导入密钥… 解锁历史 请输入恢复密钥 无法使用此恢复密钥解密备份:请检查您输入的恢复密钥是否正确。 @@ -1231,8 +1235,8 @@ 备份具有已验证设备 %s 的无效签名 备份具有未验证设备 %s 的无效签名 无法获得备份 (%s)的信任信息。 - 要在此设备上使用密钥备份,请立即使用密码或恢复密钥进行恢复。 - 备份删除中 … + 要在此会话中使用密钥备份,请立即使用密语或恢复密钥进行恢复。 + 正在删除备份… 备份(%s)删除失败 删除备份 要从此服务器中删除您备份的加密密钥吗?您将无法再使用恢复密钥来读取加密的历史消息。 @@ -1241,22 +1245,22 @@ \n \n如果您并未设置新的恢复方法,可能是有攻击者试图侵入您的账号。请立即更改您的账号密码并在设置中设定一个新的恢复方法。 那是我 - 永不丢失已加密信息 + 永不丢失已加密消息 开始使用备份密钥 - 永不丢失已加密信息 + 永不丢失已加密消息 使用备份密钥 新加密信息密钥 管理密钥备份 - 密钥备份中 … + 正在备份密钥… 所有密钥都已备份 - %d 个密钥备份中 … + 正在备份 %d 个密钥… 版本 算法 签名 忽略 - 以单点登录方式登入 + 以单点登录方式登录 无法连接到此 URL,请检查 您的设备使用了过时的 TLS 安全协议,容易受到攻击,为保证安全,您将无法进行连接 按回车发送消息 @@ -1374,17 +1378,17 @@ 拒绝 没有设置身份服务器。 服务器的错误配置导致通话失败 - 请要求您的家庭服务器 (%1$s) 的管理员配置 TURN 服务器,以使通话可靠地工作。 + 请要求您的主服务器 (%1$s) 的管理员配置 TURN 服务器,以使通话可靠地工作。 \n \n或者,您可以尝试使用 %2$s 的公共服务器,但这将不那么可靠,并且它将与该服务器共享您的 IP 地址。您也可以在“设置”中进行管理。 尝试使用 %s 不要再问我 - 设置用于帐户恢复的电子邮件,然后就可以让认识您的人选择性探索到您。 + 设置用于恢复账号的电子邮件,然后就可以让认识您的人选择性探索到您。 设定电话,然后就可以让认识您的人选择性探索到您。 - 设定电子邮件以供帐号复原。 然后就可以让认识您的人用电子邮件或电话选择性探索到您。 - 设定电子邮件以供帐号复原。 然后就可以让认识您的人用电子邮件或电话选择性探索到您。 + 设定电子邮件以供恢复账号。然后就可以让认识您的人用电子邮件或电话选择性探索到您。 + 设定电子邮件以供恢复账号。然后就可以让认识您的人用电子邮件或电话选择性探索到您。 這不是有效的 Matrix 服务器位置 - 无法在此 URL 找到家庭服务器,请检查 + 无法在此 URL 找到主服务器,请检查 允许后备呼叫协助服务器 播放 暂停 @@ -1394,7 +1398,7 @@ 通知 ${app_name} 呼叫失败 无法建立实时连接。 -\n请要求您的家庭服务器管理员配置 TURN 服务器以使通话可靠工作。 +\n请要求您的主服务器管理员配置 TURN 服务器以使通话可靠工作。 选择声音设备 电话 扬声器 @@ -1407,7 +1411,7 @@ 打开 HD SSL 错误:尚未验证对等端身份。 SSL 错误。 - 当您的家庭服务器未提供时将使用 %s 作为辅助(在通话时将分享您的 IP 地址) + 当您的主服务器未提供时将使用 %s 作为辅助(在通话时将分享您的 IP 地址) 活动通话 (%s) 返回通话 在您的设置中添加身份服务器以执行此操作。 @@ -1456,7 +1460,7 @@ 设置安全备份 重置安全备份 在此设备上设置 - 通过在您的服务器上备份加密密钥保障加密消息和数据的访问权。 + 通过在您的服务器上备份加密密钥,防止失去对加密信息和数据的访问。 为您已有的备份生成新的安全密钥或设置新的安全口令。 这将替换您的当前密钥或短语。 发现 @@ -1468,8 +1472,8 @@ %d 个封禁用户 - 公开名称(对通信参与者可见) - 会话的公开名称对通信的参与者可见 + 公开名称(对与您通讯的人可见) + 会话的公开名称对与您通讯的人可见 成功导出密钥 %1$s: %2$s %1$s: %2$s %3$s @@ -1500,18 +1504,18 @@ 未配置集成管理器。 若要继续请接受服务条款。 恢复密钥已保存。 - 您的家庭服务器上已存在备份 + 您的主服务器上已存在备份 您似乎已在另一个会话中设置密钥备份。您想要将其替换为正在创建的吗? 安全备份 保护加密信息及数据的访问权 设置安全备份 由于无效或过期的凭据您已登出。 - 验证会话已将其标记为可信。当使用端到端加密消息时信任参与者的会话将给您额外的内心平静。 - 验证会话将标记其为可信,同时将您的会话对对方标记为可信。 + 验证此会话以将其标记为可信任。当使用端对端加密消息时,信任参与者的会话可以使您更加安心。 + 验证会话将标记其为可信任,同时将您的会话对对方标记为可信任。 通过确认以下表情符号出现在对方的屏幕上来验证此会话 通过确认屏幕上对方显示以下数字来验证此会话 您收到传入验证请求。 - 与此用户的安全消息端到端加密,无法被第三方读取。 + 与此用户的安全消息端对端加密,无法被第三方读取。 对方取消了验证。 \n%s 验证已取消。 @@ -1528,7 +1532,7 @@ 用户不匹配 您未使用身份服务器 未配置身份服务器,需要重置您的密码。 - 您似乎正在试图连接到另一个家庭服务器。您想要登出吗? + 您似乎正在试图连接到另一个主服务器。您想要登出吗? 加入一个聊天室开始使用应用。 您已经跟上了! 您没有未读消息 @@ -1551,7 +1555,7 @@ 将此聊天室发布到聊天室目录 获取信任信息时发生错误 获取密钥备份数据时发生错误 - 从文件 \"%1$s\" 导入端到端密钥。 + 从文件 \"%1$s\" 导入端对端密钥。 其他第三方通知 您已经在查看此聊天室! 注册令牌 @@ -1577,7 +1581,7 @@ 发送新私聊消息 查看聊天室目录 名称或 ID (#example:matrix.org) - 在时间线中启用滑动回复 + 启用在时间线中滑动回复 在主屏幕上添加未读通知选项卡。 链接已复制到剪贴板 通过 matrix ID 添加 @@ -1617,7 +1621,7 @@ 同意身份服务器 (%s) 服务条款使您可以通过电子邮件地址或电话号码被发现。 启用详细日志。 当您发送 RageShake 时详细日志将帮助开发者提供更多日志。即使启用,应用也不会记录消息内容或任何其他私有数据。 - 接收您的家庭服务器条款和条件后请重试。 + 接收您的主服务器条款和条件后请重试。 服务器似乎响应时间太长,这可能是由于连接不良或服务器错误引起的。 请稍后再试。 发送附件 打开导航菜单 @@ -1645,10 +1649,10 @@ 贴纸 无法处理共享数据 媒体 - 此聊天室中无媒体 + 此聊天室中暂无媒体 文件 %1$s 于 %2$s - 此聊天室中无文件 + 此聊天室中暂无文件 垃圾信息 不合适的内容 自定义报告…… @@ -1668,7 +1672,7 @@ 此内容已报告为不合适。 \n \n如果您不希望再看到此用户的更多内容,您可以忽略他们以隐藏他们的消息。 - ${app_name} 需要权限在磁盘上保存您的端到端密钥。 + ${app_name} 需要权限在磁盘上保存您的端对端密钥。 \n \n请在下个弹窗中允许访问以便手动导出密钥。 目前没有网络连接 @@ -1696,7 +1700,7 @@ 扩展 & 自定义您的体验 开始吧 选择服务器 - 就像电子邮件,账户有一个家,尽管您可以与任何人聊天 + 就像电子邮件,账号有一个家,尽管您可以与任何人聊天 在最大的公共服务器上免费加入数百万用户 面向组织的高级托管 了解更多 @@ -1706,9 +1710,9 @@ 连接到 %1$s 连接到 Element Matrix 服务 连接到自定义服务器 - 登入 %1$s + 登录 %1$s 注册 - 登入 + 登录 使用单点登录继续 Element Matrix 服务地址 地址 @@ -1716,23 +1720,23 @@ 输入 Modular Element 或您想使用的服务器地址 输入您想使用的服务器的地址 载入页面时出错:%1$s (%2$d) - 应用无法登录到此家庭服务器。家庭服务器支持以下登录类型:%1$s。 + 应用无法登录到此主服务器。主服务器支持以下登录类型:%1$s。 \n \n您想要通过网页客户端登录吗? - 抱歉,此服务器不接受新账户。 - 应用无法在此服务器上创建账户。 -\n + 抱歉,此服务器不接受新账号。 + 应用无法在此服务器上创建账号。 +\n \n您想要通过网页客户端注册吗? - 电子邮件未关联到任何账户。 + 电子邮件未关联到任何账号。 在 %1$s 上重置密码 验证邮件将发送到您的收件箱以确认设置您的新密码。 下一个 电子邮件 新密码 注意! - 更改您的密码将重置所有会话上的端到端加密密钥,从而使加密聊天记录无法读取。在重设密码之前,请设置“密钥备份”或从另一个会话中导出聊天室密钥。 + 更改您的密码将重置所有会话上的端对端加密密钥,从而使加密聊天记录无法读取。在重设密码之前,请设置“密钥备份”或从另一个会话中导出聊天室密钥。 继续 - 电子邮件未链接到任何账户 + 电子邮件未链接到任何账号 检查您的收件箱 验证电子邮件已发送到 %1$s。 点击链接以确认您的新密码。跟随包含的链接验证后,请点击下方。 @@ -1746,7 +1750,7 @@ \n \n是否中止密码更改过程? 设置电子邮件地址 - 设置电子邮件用于恢复您的帐户。之后,您可以选择允许您认识的人通过电子邮件发现您。 + 设置电子邮件用于恢复您的账号。之后,您可以选择允许您认识的人通过电子邮件发现您。 电子邮件 电子邮件(可选) 下一个 @@ -1770,31 +1774,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 +1807,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 +1845,19 @@ 发生意外错误时,${app_name} 可能更经常崩溃 在明文消息前添加 ¯\\_(ツ)_/¯ 启用加密 - 一旦启用,加密无法禁用。 + 加密一经启用,便无法禁用。 您的电子邮件域无权注册此服务器 - 不可信登入 + 未信任的登录 匹配 不匹配 确认下方独特表情以相同顺序出现在他们的屏幕上,以验证此用户。 - 为了获得最高的安全性,请使用其他可信通信方式或亲自确认。 - 寻找绿色盾牌以确保用户可信。信任聊天室中的所有用户以确保聊天室的安全。 + 为了获得最高的安全性,请使用其他可信任的通讯方式,或者当面确认。 + 寻找绿色盾牌以确保用户可信任。信任聊天室中的所有用户以确保聊天室的安全。 不安全 以下其中一项可能会受到威胁: \n -\n - 您的家庭服务器 -\n - 您验证的用户连接到的家庭服务器 +\n - 您的主服务器 +\n - 您验证的用户连接到的主服务器 \n - 您或其它用户的网络连接 \n - 您或其他用户的设备 视频。 @@ -1885,10 +1889,10 @@ 为了提高安全性,请通过检查两个设备上的一次性代码来验证 %s。 \n \n为了获得最大的安全性,请亲自进行。 - 此聊天室的消息不是端到端加密。 - 该聊天室的消息端到端加密。 + 此聊天室的消息未经端对端加密。 + 该聊天室的消息已被端对端加密。 \n -\n您的消息使用锁进行保护,只有您和接收者才能使用唯一的密钥解锁。 +\n您的消息受加密保护,并且只有您和消息接收者拥有唯一解密密钥。 安全 了解更多 更多 @@ -1902,16 +1906,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 +1926,34 @@ 发送彩虹色给定表情 时间线 消息编辑器 - 启用端到端加密… - 一旦启用,加密无法禁用。 + 启用端对端加密… + 加密一经启用,便无法禁用。 是否启用加密? - 启用后,将无法禁用聊天室加密。服务器无法看到加密聊天室中发送的消息,只有聊天室的参与者才能看到。启用加密可能会阻止许多机器人和桥接正常工作。 + 聊天室加密一经启用,便无法禁用。在加密聊天室中,发送的消息无法被服务器看到,只能被聊天室的参与者看到。启用加密可能会使许多机器人和桥接无法正常运作。 启用加密 - 为保证安全,通过检查一次性代码验证 %s。 - 为保证安全,亲自或使用其他通信方式验证。 + 为保证安全,请核对一次性代码以验证 %s。 + 为保证安全,请当面验证,或者使用其他通讯方式验证。 比较独特表情,确保它们以相同顺序出现。 与其他用户设备上显示的代码比较。 - 与此用户的消息端到端加密,无法被第三方读取。 - 您的新会话已验证。它可以访问您的加密消息,其他用户会将其视为可信。 + 与此用户的消息端对端加密,无法被第三方读取。 + 您的新会话已验证。它可以访问您的加密消息,其他用户会将其视为可信任。 交叉签名 交叉签名已启用 \n设备上的私钥。 交叉签名已启用 -\n密钥可信。 +\n密钥可信任。 \n私钥未知 交叉签名已启用。 -\n密钥不可信 +\n密钥未信任 交叉签名未启用 - 您的服务器管理员已默认禁用私有聊天室和私聊消息端到端加密。 + 您的服务器管理员已默认禁用私有聊天室和私聊消息端对端加密。 激活会话 显示全部会话 管理会话 登出此会话 加密信息不可用 此会话对安全消息可信因为您已验证它: - 验证此会话以将其标记为可信,并授予其访问加密消息的权限。如果您未登录此会话,则您的帐户可能已被盗: + 验证此会话以将其标记为可信,并授予其访问加密消息的权限。如果您未登录此会话,则您的账号可能已被盗: %d 个活动会话 @@ -1962,10 +1966,10 @@ 注意 无法获取会话 会话 - 可信 - 不可信 - 此会话对加密消息可信,%1$s (%2$s) 已验证它: - %1$s (%2$s) 使用新会话登入: + 可信任 + 未信任 + 此会话可信任,可以用于收发加密消息,因为 %1$s(%2$s)已验证了它: + %1$s (%2$s) 使用新会话登录: 在此用户信任此会话之前,发送到该会话和从该会话发送的消息均标有警告。或者,您可以手动进行验证。 初始化交叉签名 重置密钥 @@ -1976,7 +1980,7 @@ 到服务器的连接已丢失 飞行模式已打开 开发工具 - 账户数据 + 账号数据 %d 票 @@ -1985,13 +1989,13 @@ 已选选项 创建简单调查 - 使用恢复短语或密钥 + 使用恢复密语或密钥 如果您无法访问已有会话 - 新登入 + 新登录 无法在存储中找到秘密 - 输入秘密存储密码 - 注意: - 您应仅在可信设备上访问秘密存储 + 输入秘密存储密语 + 警告: + 您应仅在可信任的设备上访问秘密存储 移除… 您想要发送此附件到 %1$s 吗? @@ -2010,29 +2014,29 @@ 轻按以审核和验证 使用此会话验证新的会话,授权访问加密消息。 这不是我 - 您的账户可能已被盗用 + 您的账号可能已被盗用 如果您取消,您将无法在此设备上读取加密消息,其他用户不会信任它 如果您取消,您将无法在新设备上读取加密消息,其他用户不会信任它 如果您现在取消将不会验证 %1$s (%2$s)。在他们的用户个人档案中重新开始。 以下其中一项可能有风险: \n \n- 您的密码 -\n- 您的家庭服务器 +\n- 您的主服务器 \n- 此设备或其它设备 \n- 设备使用的网络连接 \n \n我们推荐您在设置中立即更换您的密码和恢复密钥。 通过设置验证您的设备。 验证已取消 - 恢复密码 + 恢复密语 消息密钥 - 账户密码 - 设置一个 %s + 账号密码 + 设置 %s 生成消息密钥 确认 %s 输入您的 %s 以继续。 再次输入您的 %s 确认。 - 不要使用您的账户密码。 + 不要使用您的账号密码。 输入只有你知道的安全口令,用于保护服务器上的秘密。 这可能会花费数秒,请耐心等待。 设置恢复。 @@ -2042,7 +2046,7 @@ 完成 使用此 %1$s 作为安全网以防您忘记您的 %2$s。 发布创建的身份密钥 - 从密码生成安全密钥 + 从密语生成安全密钥 正在定义 SSSS 默认密钥 正在同步主密钥 正在同步用户密钥 @@ -2059,14 +2063,14 @@ 跳至已读回执 事件被聊天室管理员调整,理由:%1$s 密钥已是最新! - 保护与解锁已加密信息并信任%s。 + 保护与解锁已加密消息并信任 %s。 保存到优盘或者备份盘 复制到您的个人云存储 您无法在移动设备上执行此操作 - 设置恢复密码让您能够保护和解锁加密信息并信任设备。 + 设置恢复密语让您能够保护和解锁加密信息并信任设备。 \n \n如果您不希望设置文本密码,那么生成密钥亦可。 - 设置恢复密码让您能够保护和解锁加密信息并信任设备。 + 设置恢复密语让您能够保护和解锁加密信息并信任设备。 如果您现在取消,那么当您失去登录权限时也会丢失加密的信息和数据。 \n \n您也可以通过设置菜单来建立保护备份以及管理您的密钥。 @@ -2089,7 +2093,7 @@ 按事件设置通知重要性 以纯文本形式发送消息,而不将其解释为 markdown 用户名和/或密码不正确。输入的密码以空格开头或结尾,请检查。 - 此帐户已停用。 + 此账号已停用。 消息… 加密升级可用 启用交叉签名 @@ -2097,27 +2101,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 +2138,14 @@ 访问安全存储失败 未加密 由未验证设备加密 - 查看您的登入位置 - 验证您的全部会话确保您的账户和消息安全 - 验证访问您的账户的新登录:%1$s + 查看您的登录位置 + 验证您的全部会话确保您的账号和消息安全 + 验证访问您的账号的新登录:%1$s 使用文本手动验证 验证登录 使用表情交互式验证 通过从您的其他会话验证此登录确认您的身份,授权它访问您的加密消息。 - 标记为可信 + 标记为信任 请选择用户名。 请选择密码。 仔细检查此链接 @@ -2165,13 +2169,13 @@ 打开 %s 条款 是否从身份服务器 %s 断开? 身份服务器已过期。${app_name} 仅支持 API V2。 - 无法执行此操作。家庭服务器已过期。 + 无法执行此操作。主服务器已过期。 请先配置身份服务器。 请先在设置中接受身份服务器的条款。 为了您的隐私,${app_name} 仅支持发送用户电子邮件和电话号码的哈希值。 关联失败。 此标识符无当前关联。 - 您的家庭服务器 (%1$s) 建议使用 %2$s 作为您的身份服务器 + 您的主服务器 (%1$s) 建议使用 %2$s 作为您的身份服务器 使用 %1$s 或者,您可以输入任何其他身份服务器 URL 输入身份服务器 URL @@ -2185,14 +2189,14 @@ 启动相机 设置安全备份 安全备份 - 通过在您的服务器上备份加密密钥防止丢失对加密消息和数据的访问。 + 通过在您的服务器上备份加密密钥,防止失去对加密信息和数据的访问。 设置 使用安全密钥 生成安全密钥存储在安全的地方如密码管理器或保险箱。 使用安全口令 输入仅有您知道的安全口令,生成备份用的密钥。 保存您的安全密钥 - 将您的安全密钥存储在安全的地方如密码管理器或保险箱。 + 将您的安全密钥存储在安全的地方,例如密码管理器或保险箱。 设置安全口令 输入只有您知道的安全口令,用于保护您的服务器上的秘密。 安全口令 @@ -2205,13 +2209,13 @@ 您无法访问此消息 正在等待此消息,可能会花费一些时间 无法解密 - 由于端到端加密,您可能需要等待某人的消息到达因为加密密钥未正确发送给您。 + 由于端对端加密,您可能需要等待某人的消息到达因为加密密钥未正确发送给您。 您无法访问此消息因为您已屏蔽此发送者 - 您无法访问此消息因为您的会话不被发送者信任 + 您无法访问此消息,因为您的会话不被发送者信任 您无法访问此消息因为发送者有意不发送密钥 正在等待加密历史 Riot 现在是 Element! - 我们兴奋地宣布我们改名了!您的应用已经是最新的并且您已登入您的帐户。 + 我们兴奋地宣布我们改名了!您的应用已经是最新的并且您已登录您的账号。 明白了 了解更多 将恢复密钥保存到 @@ -2247,7 +2251,7 @@ 发起音频会议 会议使用 Jitsi 安全与许可政策。您的会议进行期间当前聊天室内的所有人将看到加入邀请。 您无法呼叫您自己 - 您无法呼叫您自己,请等待参与者接受邀请 + 您无法与自己通话,请等待参与者接受邀请 添加小部件失败 移除小部件失败 @@ -2267,14 +2271,14 @@ 注意!登出前最后一次尝试! 错误次数过多,您已被登出 此电话号码已定义。 - 您的帐户尚未添加电话号码 + 您的账号尚未添加电话号码 电子邮件地址 - 您的账户尚未添加电子邮件 + 您的账号尚未添加电子邮件 电话号码 移除 %s? 请确认您已点击我们向您发送的电子邮件中的链接。 电子邮件和电话号码 - 管理链接到您的 Matrix 账户的电子邮件和电话号码 + 管理链接到您的 Matrix 账号的电子邮件和电话号码 代码 请使用国际格式(电话号码必须以 ‘+’ 开始) 通过验证此登录确认您的身份,授权它访问加密信息。 @@ -2290,7 +2294,7 @@ 机器人按钮 回应:%s 验证结果 - 是否删除类型 %1$s 的账户数据? + 是否删除类型 %1$s 的账号数据? \n \n小心使用,它可能导致意外行为。 链接格式不正确 @@ -2313,17 +2317,17 @@ 如果您重置一切 仅当没有其他设备可用来验证此设备时,才执行此操作。 重置一切 - 忘记或丢失了所有的回复选项?重置一切 + 忘记或丢失了所有的恢复选项?重置一切 您已加入。 %s 已加入。 - 此聊天室的消息是端到端加密的。 + 此聊天室的消息是端对端加密的。 离开 设置 - 此处的消息是端到端加密的, + 此处的消息已被端对端加密。 \n -\n您的消息已用锁保护并且只有您和收件人拥有唯一的钥匙解锁它们。 - 此处的消息不是端到端加密。 - 此家庭服务器正在运行较旧版本。要求您的家庭服务器管理员升级。您可以继续,但一些功能可能无法正确工作。 +\n您的消息受加密保护,并且只有您和消息接收者拥有唯一解密密钥。 + 此处的消息未经端对端加密。 + 此主服务器正在运行较旧版本。要求您的主服务器管理员升级。您可以继续,但一些功能可能无法正确工作。 您仅发出此邀请。 %1$s 仅发出此邀请。 在加密聊天室显示完整历史 @@ -2361,7 +2365,7 @@ 聊天室地址 查看和管理此聊天室的地址,以及它在聊天室目录中的可见性。 聊天室地址 - 聊天室访问 + 聊天室访问权限 改成谁都可以阅读历史只会应用于此聊天室未来的消息。已经存在的历史消息的可见性将不会改变。 发送密钥共享请求历史记录 取消发布 @@ -2423,7 +2427,8 @@ 聊天室话题(可选) 聊天室名称 此聊天室无法预览。您想加入吗? - 此聊天室当前不可访问。请稍候重试,或向聊天室管理员询问以检查您是否拥有访问权限。 + 目前无法访问此聊天室。 +\n请稍候重试,或询问聊天室管理员以确认您是否拥有访问权限。 无法获取当前聊天室目录可见性(%1$s)。 是否将此聊天室发布至 %1$s 的聊天室目录中? 取消发布此地址 @@ -2473,7 +2478,87 @@ 系统默认 • 匹配 %s 的服务器已被屏蔽。 • IP 地址匹配的服务器已被屏蔽。 - • IP 地址符合的服务器已被允许。 - • 符合 %s 的服务器已被屏蔽。 - • 符合 %s 的服务器已被允许。 + • 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 为此聊天室设置了服务器访问控制列表。 + 您为此聊天室设置了服务器访问控制列表。 \ No newline at end of file From fa3ccda3f414ca83a8f2006ee69ed01c7d7fafb9 Mon Sep 17 00:00:00 2001 From: RainSlide Date: Thu, 25 Mar 2021 06:12:36 +0000 Subject: [PATCH 098/249] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (13 of 13 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/zh_Hans/ --- .../android/zh-Hans/changelogs/40100100.txt | 2 +- .../android/zh-Hans/changelogs/40100120.txt | 2 ++ .../android/zh-Hans/changelogs/40100130.txt | 2 ++ .../android/zh-Hans/changelogs/40100140.txt | 2 ++ .../android/zh-Hans/changelogs/40100150.txt | 2 ++ .../android/zh-Hans/changelogs/40100160.txt | 2 ++ .../android/zh-Hans/changelogs/40100170.txt | 2 ++ .../android/zh-Hans/changelogs/40101000.txt | 2 ++ .../android/zh-Hans/changelogs/40101010.txt | 2 ++ .../android/zh-Hans/full_description.txt | 32 +++++++++---------- .../android/zh-Hans/short_description.txt | 2 +- 11 files changed, 34 insertions(+), 18 deletions(-) create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/40100120.txt create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/40100130.txt create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/40100140.txt create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/40100150.txt create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/40100160.txt create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/40100170.txt create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/40101000.txt create mode 100644 fastlane/metadata/android/zh-Hans/changelogs/40101010.txt 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 通话。保护您的数据不被第三方窃取。 From 837948938588797e4f34510b5a3b43ef396c2853 Mon Sep 17 00:00:00 2001 From: vachan-maker Date: Thu, 25 Mar 2021 08:15:03 +0000 Subject: [PATCH 099/249] Translated using Weblate (Malayalam) Currently translated at 17.3% (410 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ml/ --- vector/src/main/res/values-ml/strings.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/vector/src/main/res/values-ml/strings.xml b/vector/src/main/res/values-ml/strings.xml index 7be6c5865e..17e8c5cf7b 100644 --- a/vector/src/main/res/values-ml/strings.xml +++ b/vector/src/main/res/values-ml/strings.xml @@ -416,4 +416,18 @@ നിങ്ങൾ ഒരു ചിത്രം അയച്ചു. %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-ന്റെ ക്ഷണം + നിങ്ങൾ ഒരു സ്റ്റിക്കർ അയച്ചു. \ No newline at end of file From a7539d0f9508cfe40347cdccd73147ffee3cdda3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Mar 2021 12:15:06 +0100 Subject: [PATCH 100/249] Be more robust when parsing some enums --- CHANGES.md | 1 + .../room/model/RoomHistoryVisibilityContent.kt | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 68b6194734..53383c14de 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,7 @@ Improvements 🙌: - Improve message with Emoji only detection (#3017) - Picture preview when replying. Also add the image preview in the message detail bottomsheet (#2916) - Api interceptor to allow app developers peek responses (#2986) + - Be more robust when parsing some enums Bugfix 🐛: - Fix bad theme change for the MainActivity diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt index 31493be7ea..b16d81dbaa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt @@ -18,8 +18,20 @@ package org.matrix.android.sdk.api.session.room.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import timber.log.Timber @JsonClass(generateAdapter = true) data class RoomHistoryVisibilityContent( - @Json(name = "history_visibility") val historyVisibility: RoomHistoryVisibility? = null -) + @Json(name = "history_visibility") private val _historyVisibility: String? = null +) { + val historyVisibility: RoomHistoryVisibility? = when (_historyVisibility) { + "world_readable" -> RoomHistoryVisibility.WORLD_READABLE + "shared" -> RoomHistoryVisibility.SHARED + "invited" -> RoomHistoryVisibility.INVITED + "joined" -> RoomHistoryVisibility.JOINED + else -> { + Timber.w("Invalid value for RoomHistoryVisibility: `$_historyVisibility`") + null + } + } +} From 5364d7fd03d1252785eb89c80afa9c2dee623b32 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Mar 2021 12:18:36 +0100 Subject: [PATCH 101/249] Be more robust when parsing some enums - Guest Access --- .../session/room/model/RoomGuestAccessContent.kt | 14 ++++++++++++-- .../session/room/state/DefaultStateService.kt | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt index 99b035d30e..8eea0d3d06 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.room.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import timber.log.Timber /** * Class representing the EventType.STATE_ROOM_GUEST_ACCESS state event content @@ -26,8 +27,17 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class RoomGuestAccessContent( // Required. Whether guests can join the room. One of: ["can_join", "forbidden"] - @Json(name = "guest_access") val guestAccess: GuestAccess? = null -) + @Json(name = "guest_access") private val _guestAccess: String? = null +) { + val guestAccess: GuestAccess? = when (_guestAccess) { + "can_join" -> GuestAccess.CanJoin + "forbidden" -> GuestAccess.Forbidden + else -> { + Timber.w("Invalid value for GuestAccess: `$_guestAccess`") + null + } + } +} @JsonClass(generateAdapter = false) enum class GuestAccess(val value: String) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index f2640fd1e7..3c42b77ae8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -138,7 +138,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private if (guestAccess != null) { sendStateEvent( eventType = EventType.STATE_ROOM_GUEST_ACCESS, - body = RoomGuestAccessContent(guestAccess).toContent(), + body = mapOf("guest_access" to guestAccess), stateKey = null ) } From ee265f71e68b6acec22935c6d9663e754aa6b892 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Mar 2021 12:22:11 +0100 Subject: [PATCH 102/249] Be more robust when parsing some enums - RoomJoinRules --- .../session/room/model/RoomJoinRulesContent.kt | 18 +++++++++++++++--- .../session/room/state/DefaultStateService.kt | 2 +- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt index 3be2d38be7..8078951ad0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt @@ -19,11 +19,23 @@ package org.matrix.android.sdk.api.session.room.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import timber.log.Timber /** * Class representing the EventType.STATE_ROOM_JOIN_RULES state event content */ @JsonClass(generateAdapter = true) -data class RoomJoinRulesContent( - @Json(name = "join_rule") val joinRules: RoomJoinRules? = null -) +data class RoomJoinRulesContent constructor( + @Json(name = "join_rule") private val _joinRules: String? = null +) { + val joinRules: RoomJoinRules? = when (_joinRules) { + "public" -> RoomJoinRules.PUBLIC + "invite" -> RoomJoinRules.INVITE + "knock" -> RoomJoinRules.KNOCK + "private" -> RoomJoinRules.PRIVATE + else -> { + Timber.w("Invalid value for RoomJoinRules: `$_joinRules`") + null + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index 3c42b77ae8..ebccd84f39 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -131,7 +131,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private if (joinRules != null) { sendStateEvent( eventType = EventType.STATE_ROOM_JOIN_RULES, - body = RoomJoinRulesContent(joinRules).toContent(), + body = mapOf("join_rule" to joinRules), stateKey = null ) } From 2e23fec82baf3d1ec85f6ef458031a53b574cb5d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Mar 2021 12:36:33 +0100 Subject: [PATCH 103/249] Sadly fields cannot be private --- .../sdk/api/session/room/model/RoomGuestAccessContent.kt | 2 +- .../api/session/room/model/RoomHistoryVisibilityContent.kt | 2 +- .../sdk/api/session/room/model/RoomJoinRulesContent.kt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt index 8eea0d3d06..24a56b6b56 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt @@ -27,7 +27,7 @@ import timber.log.Timber @JsonClass(generateAdapter = true) data class RoomGuestAccessContent( // Required. Whether guests can join the room. One of: ["can_join", "forbidden"] - @Json(name = "guest_access") private val _guestAccess: String? = null + @Json(name = "guest_access") val _guestAccess: String? = null ) { val guestAccess: GuestAccess? = when (_guestAccess) { "can_join" -> GuestAccess.CanJoin diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt index b16d81dbaa..3ac14e48de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt @@ -22,7 +22,7 @@ import timber.log.Timber @JsonClass(generateAdapter = true) data class RoomHistoryVisibilityContent( - @Json(name = "history_visibility") private val _historyVisibility: String? = null + @Json(name = "history_visibility") val _historyVisibility: String? = null ) { val historyVisibility: RoomHistoryVisibility? = when (_historyVisibility) { "world_readable" -> RoomHistoryVisibility.WORLD_READABLE diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt index 8078951ad0..8082486b22 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt @@ -25,8 +25,8 @@ import timber.log.Timber * Class representing the EventType.STATE_ROOM_JOIN_RULES state event content */ @JsonClass(generateAdapter = true) -data class RoomJoinRulesContent constructor( - @Json(name = "join_rule") private val _joinRules: String? = null +data class RoomJoinRulesContent( + @Json(name = "join_rule") val _joinRules: String? = null ) { val joinRules: RoomJoinRules? = when (_joinRules) { "public" -> RoomJoinRules.PUBLIC From 297fff1394b201596c2d76dcc40209cf82e2aead Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Mar 2021 13:41:10 +0100 Subject: [PATCH 104/249] Cleanup some Enums --- .../sdk/api/session/room/model/Membership.kt | 25 ++++++------------- .../room/model/RoomGuestAccessContent.kt | 9 +++---- .../api/session/room/model/RoomJoinRules.kt | 18 ++++--------- .../sdk/internal/session/room/RoomAPI.kt | 5 ++-- .../room/membership/LoadRoomMembersTask.kt | 2 +- 5 files changed, 19 insertions(+), 40 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Membership.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Membership.kt index 5844aead8d..a5d0f63722 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Membership.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Membership.kt @@ -23,24 +23,13 @@ import com.squareup.moshi.JsonClass * Represents the membership of a user on a room */ @JsonClass(generateAdapter = false) -enum class Membership(val value: String) { - - NONE("none"), - - @Json(name = "invite") - INVITE("invite"), - - @Json(name = "join") - JOIN("join"), - - @Json(name = "knock") - KNOCK("knock"), - - @Json(name = "leave") - LEAVE("leave"), - - @Json(name = "ban") - BAN("ban"); +enum class Membership { + NONE, + @Json(name = "invite") INVITE, + @Json(name = "join") JOIN, + @Json(name = "knock") KNOCK, + @Json(name = "leave") LEAVE, + @Json(name = "ban") BAN; fun isLeft(): Boolean { return this == KNOCK || this == LEAVE || this == BAN diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt index 24a56b6b56..0760c6f1b4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt @@ -40,10 +40,7 @@ data class RoomGuestAccessContent( } @JsonClass(generateAdapter = false) -enum class GuestAccess(val value: String) { - @Json(name = "can_join") - CanJoin("can_join"), - - @Json(name = "forbidden") - Forbidden("forbidden") +enum class GuestAccess { + @Json(name = "can_join") CanJoin, + @Json(name = "forbidden") Forbidden } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt index 09aacfabbe..f3e8d357f3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt @@ -24,17 +24,9 @@ import com.squareup.moshi.JsonClass * Enum for [RoomJoinRulesContent] : https://matrix.org/docs/spec/client_server/r0.4.0#m-room-join-rules */ @JsonClass(generateAdapter = false) -enum class RoomJoinRules(val value: String) { - - @Json(name = "public") - PUBLIC("public"), - - @Json(name = "invite") - INVITE("invite"), - - @Json(name = "knock") - KNOCK("knock"), - - @Json(name = "private") - PRIVATE("private") +enum class RoomJoinRules { + @Json(name = "public") PUBLIC, + @Json(name = "invite") INVITE, + @Json(name = "knock") KNOCK, + @Json(name = "private") PRIVATE } 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 20cb49ee8a..b065a30fc9 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 @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse import org.matrix.android.sdk.api.util.JsonDict @@ -100,8 +101,8 @@ internal interface RoomAPI { @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/members") fun getMembers(@Path("roomId") roomId: String, @Query("at") syncToken: String?, - @Query("membership") membership: String?, - @Query("not_membership") notMembership: String? + @Query("membership") membership: Membership?, + @Query("not_membership") notMembership: Membership? ): Call /** 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 cc491d1cd9..6adf3c59d1 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 @@ -91,7 +91,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( val lastToken = syncTokenStore.getLastToken() val response = try { executeRequest(globalErrorReceiver) { - apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership?.value) + apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership) } } catch (throwable: Throwable) { // Revert status to NONE From d75b7434cf8c200609dd24dea41ab35fee5068e4 Mon Sep 17 00:00:00 2001 From: TR-SLimey <37966924+TR-SLimey@users.noreply.github.com> Date: Fri, 26 Mar 2021 12:45:22 +0000 Subject: [PATCH 105/249] Replace dangerous characters in the filename with underscores --- .../im/vector/app/core/utils/ExternalApplicationsUtil.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt index fa1c50f419..859df7d714 100644 --- a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt +++ b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt @@ -514,7 +514,7 @@ fun selectTxtFileToWrite( @Suppress("DEPRECATION") fun saveFileIntoLegacy(sourceFile: File, dstDirPath: File, outputFilename: String?): File? { // defines another name for the external media - val dstFileName: String + var dstFileName: String // build a filename is not provided if (null == outputFilename) { @@ -529,6 +529,9 @@ fun saveFileIntoLegacy(sourceFile: File, dstDirPath: File, outputFilename: Strin dstFileName = outputFilename } + // remove dangerous characters from the filename + dstFileName = dstFileName.replace(Regex("""[/\\]"""), "_") + var dstFile = File(dstDirPath, dstFileName) // if the file already exists, append a marker From 94220a24d147ce1d6cea3b873d94f64a8b72d527 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Mar 2021 13:58:34 +0100 Subject: [PATCH 106/249] cleanup --- .../sdk/internal/session/room/state/DefaultStateService.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index ebccd84f39..615bc99096 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -27,10 +27,8 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent -import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomJoinRules -import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.state.StateService import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.MimeTypes From a40adb903af21802abbf2e47877c44e886847be0 Mon Sep 17 00:00:00 2001 From: oogm Date: Fri, 26 Mar 2021 17:21:02 +0100 Subject: [PATCH 107/249] Update emoji import script to ease execution, output smaller .json files and capitalize emoji names --- tools/import_emojis.py | 25 ++++++++++++++++--- .../main/res/raw/emoji_picker_datasource.json | 2 +- 2 files changed, 22 insertions(+), 5 deletions(-) mode change 100644 => 100755 tools/import_emojis.py diff --git a/tools/import_emojis.py b/tools/import_emojis.py old mode 100644 new mode 100755 index 6f204c23f2..f5638175a9 --- a/tools/import_emojis.py +++ b/tools/import_emojis.py @@ -1,10 +1,15 @@ +#!/usr/bin/env python3 from collections import OrderedDict import requests import json import re +import os from bs4 import BeautifulSoup +# A list of words to not capitalize in emoji-names +capitalization_exclude = {'with', 'a', 'at', 'of', 'for', 'and', 'over', 'the', 'off', 'on', 'out', 'in', 'but', 'or'} + # Create skeleton of the final json file as a python dictionary: emoji_picker_datasource = { "compressed": True, @@ -17,6 +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...",) req = requests.get("https://unicode.org/emoji/charts/emoji-list.html") soup = BeautifulSoup(req.content, 'html.parser') @@ -24,6 +30,7 @@ soup = BeautifulSoup(req.content, 'html.parser') table = soup.body.table # Go over all rows +print("Extracting emojis...") for row in table.find_all('tr'): # Add "bigheads" rows to categories if 'bighead' in next(row.children)['class']: @@ -55,6 +62,11 @@ for row in table.find_all('tr'): emoji_id = emoji_id.strip() # Remove leading/trailing whitespaces emoji_id = emoji_id.replace(' ', '-') + # Capitalize name according to the same rules as the previous emoji_picker_datasource.json + # - Words are separated by any non-word character (\W), e.g. space, comma, parentheses, dots, etc. + # - Words are capitalized if they are either at the beginning of the name OR not in capitalization_exclude (extracted from the previous datasource, too) + emoji_name_cap = "".join([w.capitalize() if i == 0 or w not in capitalization_exclude else w for i, w in enumerate(re.split('(\W)', emoji_name))]) + # Extract emoji unicode-codepoint emoji_code_raw = code_element.text emoji_code_list = emoji_code_raw.split(" ") @@ -69,7 +81,7 @@ for row in table.find_all('tr'): # Add the emoji itself to the "emojis" dict emoji_picker_datasource_emojis[emoji_id] = { - "a": emoji_name, + "a": emoji_name_cap, "b": emoji_code, "j": emoji_keywords } @@ -78,10 +90,12 @@ for row in table.find_all('tr'): # There is no official specification of keywords beyond that, but muan/emojilib maintains a well maintained and # established repository with additional keywords. We extend our list with the keywords from there. # At the time of writing it had additional keyword information for all emojis except a few from the newest unicode 13.1. +print("Fetching additional keywords from Emojilib...") req = requests.get("https://raw.githubusercontent.com/muan/emojilib/main/dist/emoji-en-US.json") emojilib_data = json.loads(req.content) # We just go over all the official emojis from unicode, and add the keywords there +print("Adding keywords to emojis...") for emoji in emoji_picker_datasource_emojis: emoji_name = emoji_picker_datasource_emojis[emoji]["a"] emoji_code = emoji_picker_datasource_emojis[emoji]["b"] @@ -95,7 +109,7 @@ for emoji in emoji_picker_datasource_emojis: elif emoji_unicode+chr(0xfe0f) in emojilib_data: emoji_additional_keywords = emojilib_data[emoji_unicode+chr(0xfe0f)] else: - print("No additional keywords for", emoji_unicode, emoji_picker_datasource_emojis[emoji]) + print("* No additional keywords for", emoji_unicode, emoji_picker_datasource_emojis[emoji]) continue # If additional keywords exist, add them to emoji_picker_datasource_emojis @@ -110,5 +124,8 @@ for emoji in emoji_picker_datasource_emojis: emoji_picker_datasource['categories'] = [x for x in emoji_picker_datasource['categories'] if x['id'] != 'component'] # Write result to file (overwrite previous), without escaping unicode characters -with open("../vector/src/main/res/raw/emoji_picker_datasource.json", "w") as outfile: - json.dump(emoji_picker_datasource, outfile, ensure_ascii=False) +print("Writing emoji_picker_datasource.json...") +scripts_dir = os.path.dirname(os.path.abspath(__file__)) +with open(os.path.join(scripts_dir, "../vector/src/main/res/raw/emoji_picker_datasource.json"), "w") as outfile: + json.dump(emoji_picker_datasource, outfile, ensure_ascii=False, separators=(',', ':')) +print("Done.") diff --git a/vector/src/main/res/raw/emoji_picker_datasource.json b/vector/src/main/res/raw/emoji_picker_datasource.json index e0314e9a77..cc676a4dd0 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", "face", "smile", "happy"]}, "grinning-face-with-big-eyes": {"a": "grinning face with big eyes", "b": "1F603", "j": ["joy", ":)", ":D", "open", "face", "smile", "mouth", "haha", "happy", "funny"]}, "grinning-face-with-smiling-eyes": {"a": "grinning face with smiling eyes", "b": "1F604", "j": ["like", "joy", ":)", ":D", "open", "face", "smile", "eye", "mouth", "haha", "laugh", "happy", "funny"]}, "beaming-face-with-smiling-eyes": {"a": "beaming face with smiling eyes", "b": "1F601", "j": ["grin", "joy", "face", "smile", "eye", "kawaii", "happy"]}, "grinning-squinting-face": {"a": "grinning squinting face", "b": "1F606", "j": ["joy", "XD", "face", "smile", "satisfied", "mouth", "laugh", "haha", "happy", "lol", "glad"]}, "grinning-face-with-sweat": {"a": "grinning face with sweat", "b": "1F605", "j": ["sweat", "relief", "hot", "cold", "open", "face", "smile", "laugh", "happy"]}, "rolling-on-the-floor-laughing": {"a": "rolling on the floor laughing", "b": "1F923", "j": ["lol", "rolling", "rotfl", "laughing", "face", "laugh", "haha", "floor", "rofl"]}, "face-with-tears-of-joy": {"a": "face with tears of joy", "b": "1F602", "j": ["cry", "tears", "joy", "weep", "face", "laugh", "haha", "happy", "happytears", "tear"]}, "slightly-smiling-face": {"a": "slightly smiling face", "b": "1F642", "j": ["smile", "face"]}, "upsidedown-face": {"a": "upside-down face", "b": "1F643", "j": ["face", "smile", "upside_down_face", "silly", "upside-down", "flipped"]}, "winking-face": {"a": "winking face", "b": "1F609", "j": [";)", "secret", "mischievous", "face", "smile", "eye", "happy", "wink"]}, "smiling-face-with-smiling-eyes": {"a": "smiling face with smiling eyes", "b": "1F60A", "j": ["embarrassed", "joy", "flushed", "blush", "face", "smile", "eye", "happy", "shy", "crush"]}, "smiling-face-with-halo": {"a": "smiling face with halo", "b": "1F607", "j": ["heaven", "innocent", "face", "fantasy", "angel", "halo"]}, "smiling-face-with-hearts": {"a": "smiling face with hearts", "b": "1F970", "j": ["love", "like", "infatuation", "face", "valentines", "hearts", "in love", "adore", "affection", "crush"]}, "smiling-face-with-hearteyes": {"a": "smiling face with heart-eyes", "b": "1F60D", "j": ["love", "like", "heart", "infatuation", "face", "smile", "eye", "valentines", "affection", "smiling_face_with_heart_eyes", "crush"]}, "starstruck": {"a": "star-struck", "b": "1F929", "j": ["grinning", "star", "face", "star_struck", "starry", "smile", "starry-eyed", "eyes"]}, "face-blowing-a-kiss": {"a": "face blowing a kiss", "b": "1F618", "j": ["love", "like", "infatuation", "face", "valentines", "affection", "kiss"]}, "kissing-face": {"a": "kissing face", "b": "1F617", "j": ["love", "like", "infatuation", "3", "face", "valentines", "kiss"]}, "smiling-face": {"a": "smiling face", "b": "263A", "j": ["relaxed", "happiness", "massage", "blush", "face", "outlined", "smile"]}, "kissing-face-with-closed-eyes": {"a": "kissing face with closed eyes", "b": "1F61A", "j": ["love", "like", "infatuation", "face", "eye", "valentines", "closed", "affection", "kiss"]}, "kissing-face-with-smiling-eyes": {"a": "kissing face with smiling eyes", "b": "1F619", "j": ["infatuation", "face", "smile", "valentines", "eye", "affection", "kiss"]}, "smiling-face-with-tear": {"a": "smiling face with tear", "b": "1F972", "j": ["grateful", "relieved", "proud", "pretend", "touched", "smiling", "cry", "sad", "tear"]}, "face-savoring-food": {"a": "face savoring food", "b": "1F60B", "j": ["tongue", "joy", "yummy", "silly", "face", "savouring", "smile", "happy", "nom", "delicious", "yum"]}, "face-with-tongue": {"a": "face with tongue", "b": "1F61B", "j": ["tongue", "mischievous", "playful", "face", "smile", "prank", "childish"]}, "winking-face-with-tongue": {"a": "winking face with tongue", "b": "1F61C", "j": ["tongue", "playful", "mischievous", "face", "smile", "eye", "joke", "wink", "prank", "childish"]}, "zany-face": {"a": "zany face", "b": "1F92A", "j": ["large", "crazy", "face", "goofy", "eye", "small"]}, "squinting-face-with-tongue": {"a": "squinting face with tongue", "b": "1F61D", "j": ["tongue", "playful", "mischievous", "taste", "face", "horrible", "eye", "smile", "prank"]}, "moneymouth-face": {"a": "money-mouth face", "b": "1F911", "j": ["rich", "face", "dollar", "mouth", "money_mouth_face", "money"]}, "hugging-face": {"a": "hugging face", "b": "1F917", "j": ["smile", "hugging", "face", "hug"]}, "face-with-hand-over-mouth": {"a": "face with hand over mouth", "b": "1F92D", "j": ["whoops", "face", "sudden realization", "shock", "surprise"]}, "shushing-face": {"a": "shushing face", "b": "1F92B", "j": ["quiet", "shush", "face", "shhh"]}, "thinking-face": {"a": "thinking face", "b": "1F914", "j": ["think", "consider", "hmmm", "thinking", "face"]}, "zippermouth-face": {"a": "zipper-mouth face", "b": "1F910", "j": ["secret", "zipper_mouth_face", "face", "sealed", "mouth", "zipper"]}, "face-with-raised-eyebrow": {"a": "face with raised eyebrow", "b": "1F928", "j": ["disbelief", "skeptic", "disapproval", "face", "mild surprise", "scepticism", "distrust", "surprise"]}, "neutral-face": {"a": "neutral face", "b": "1F610", "j": ["deadpan", "neutral", "face", "meh", "indifference", ":|"]}, "expressionless-face": {"a": "expressionless face", "b": "1F611", "j": ["inexpressive", "indifferent", "deadpan", "-_-", "face", "meh", "expressionless", "unexpressive"]}, "face-without-mouth": {"a": "face without mouth", "b": "1F636", "j": ["hellokitty", "face", "quiet", "mouth", "silent"]}, "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": ["smug", "face", "smile", "sarcasm", "mean", "prank", "smirk"]}, "unamused-face": {"a": "unamused face", "b": "1F612", "j": ["side_eye", "bored", "unamused", "unhappy", "straight face", "face", "skeptical", "dubious", "sarcasm", "indifference", "serious", "unimpressed"]}, "face-with-rolling-eyes": {"a": "face with rolling eyes", "b": "1F644", "j": ["rolling", "frustrated", "face", "eyeroll", "eyes"]}, "grimacing-face": {"a": "grimacing face", "b": "1F62C", "j": ["grimace", "teeth", "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": ["pinocchio", "face", "lie"]}, "relieved-face": {"a": "relieved face", "b": "1F60C", "j": ["relaxed", "massage", "happiness", "relieved", "phew", "face"]}, "pensive-face": {"a": "pensive face", "b": "1F614", "j": ["depressed", "upset", "pensive", "face", "sad", "dejected"]}, "sleepy-face": {"a": "sleepy face", "b": "1F62A", "j": ["sleep", "tired", "face", "nap", "rest"]}, "drooling-face": {"a": "drooling face", "b": "1F924", "j": ["face", "drooling"]}, "sleeping-face": {"a": "sleeping face", "b": "1F634", "j": ["night", "sleep", "tired", "zzz", "face", "sleepy"]}, "face-with-medical-mask": {"a": "face with medical mask", "b": "1F637", "j": ["ill", "mask", "cold", "face", "doctor", "disease", "sick"]}, "face-with-thermometer": {"a": "face with thermometer", "b": "1F912", "j": ["ill", "thermometer", "fever", "cold", "face", "temperature", "sick"]}, "face-with-headbandage": {"a": "face with head-bandage", "b": "1F915", "j": ["face_with_head_bandage", "face", "injury", "clumsy", "hurt", "injured", "bandage"]}, "nauseated-face": {"a": "nauseated face", "b": "1F922", "j": ["ill", "throw up", "gross", "face", "green", "sick", "vomit", "nauseated"]}, "face-vomiting": {"a": "face vomiting", "b": "1F92E", "j": ["sick", "vomit", "face", "puke"]}, "sneezing-face": {"a": "sneezing face", "b": "1F927", "j": ["gesundheit", "face", "sick", "sneeze", "allergy"]}, "hot-face": {"a": "hot face", "b": "1F975", "j": ["sweating", "heat", "hot", "red-faced", "feverish", "face", "red", "heat stroke"]}, "cold-face": {"a": "cold face", "b": "1F976", "j": ["blue", "blue-faced", "frostbite", "freezing", "cold", "face", "icicles", "frozen"]}, "woozy-face": {"a": "woozy face", "b": "1F974", "j": ["face", "intoxicated", "dizzy", "wavy mouth", "tipsy", "uneven eyes", "wavy"]}, "knockedout-face": {"a": "knocked-out face", "b": "1F635", "j": ["knocked out", "dizzy_face", "unconscious", "face", "spent", "dizzy", "dead", "xox"]}, "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": ["face", "shocked", "mind blown", "blown", "mind"]}, "cowboy-hat-face": {"a": "cowboy hat face", "b": "1F920", "j": ["hat", "cowboy", "face", "cowgirl"]}, "partying-face": {"a": "partying face", "b": "1F973", "j": ["face", "hat", "celebration", "horn", "party", "woohoo"]}, "disguised-face": {"a": "disguised face", "b": "1F978", "j": ["moustache", "glasses", "face", "brows", "disguise", "pretent", "nose", "incognito"]}, "smiling-face-with-sunglasses": {"a": "smiling face with sunglasses", "b": "1F60E", "j": ["sun", "cool", "sunglasses", "bright", "face", "sunglass", "smile", "summer", "beach"]}, "nerd-face": {"a": "nerd face", "b": "1F913", "j": ["nerdy", "geek", "nerd", "face", "dork"]}, "face-with-monocle": {"a": "face with monocle", "b": "1F9D0", "j": ["wealthy", "face", "stuffy"]}, "confused-face": {"a": "confused face", "b": "1F615", "j": ["hmmm", "face", "huh", "meh", "weird", "indifference", "confused", ":/"]}, "worried-face": {"a": "worried face", "b": "1F61F", "j": ["nervous", ":(", "face", "concern", "worried"]}, "slightly-frowning-face": {"a": "slightly frowning face", "b": "1F641", "j": ["frowning", "upset", "disappointed", "frown", "face", "sad"]}, "frowning-face": {"a": "frowning face", "b": "2639", "j": ["frown", "upset", "sad", "face"]}, "face-with-open-mouth": {"a": "face with open mouth", "b": "1F62E", "j": [":O", "open", "face", "wow", "impressed", "mouth", "whoa", "sympathy", "surprise"]}, "hushed-face": {"a": "hushed face", "b": "1F62F", "j": ["shh", "stunned", "face", "hushed", "surprised", "woo"]}, "astonished-face": {"a": "astonished face", "b": "1F632", "j": ["face", "shocked", "surprised", "astonished", "totally", "poisoned", "xox"]}, "flushed-face": {"a": "flushed face", "b": "1F633", "j": ["flattered", "blush", "face", "dazed", "flushed", "shy"]}, "pleading-face": {"a": "pleading face", "b": "1F97A", "j": ["puppy eyes", "begging", "face", "mercy"]}, "frowning-face-with-open-mouth": {"a": "frowning face with open mouth", "b": "1F626", "j": ["frown", "open", "face", "mouth", "what", "aw"]}, "anguished-face": {"a": "anguished face", "b": "1F627", "j": ["stunned", "nervous", "face", "anguished"]}, "fearful-face": {"a": "fearful face", "b": "1F628", "j": ["oops", "nervous", "face", "scared", "huh", "fear", "terrified", "fearful"]}, "anxious-face-with-sweat": {"a": "anxious face with sweat", "b": "1F630", "j": ["blue", "sweat", "nervous", "cold", "face", "rushed"]}, "sad-but-relieved-face": {"a": "sad but relieved face", "b": "1F625", "j": ["disappointed", "nervous", "sweat", "relieved", "whew", "phew", "face"]}, "crying-face": {"a": "crying face", "b": "1F622", "j": ["depressed", "upset", "tears", "face", ":'(", "cry", "sad", "tear"]}, "loudly-crying-face": {"a": "loudly crying face", "b": "1F62D", "j": ["depressed", "upset", "tears", "face", "cry", "sad", "tear", "sob"]}, "face-screaming-in-fear": {"a": "face screaming in fear", "b": "1F631", "j": ["munch", "face", "omg", "scared", "scream", "fear"]}, "confounded-face": {"a": "confounded face", "b": "1F616", "j": ["confounded", "oops", ":S", "sick", "unwell", "face", "confused"]}, "persevering-face": {"a": "persevering face", "b": "1F623", "j": ["oops", "upset", "persevere", "no", "face", "sick"]}, "disappointed-face": {"a": "disappointed face", "b": "1F61E", "j": ["depressed", "upset", "disappointed", ":(", "face", "sad"]}, "downcast-face-with-sweat": {"a": "downcast face with sweat", "b": "1F613", "j": ["sweat", "tired", "hot", "cold", "face", "exercise", "sad"]}, "weary-face": {"a": "weary face", "b": "1F629", "j": ["upset", "tired", "frustrated", "face", "sleepy", "sad", "weary"]}, "tired-face": {"a": "tired face", "b": "1F62B", "j": ["upset", "whine", "tired", "frustrated", "face", "sick"]}, "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": ["triumph", "gas", "pride", "phew", "face", "proud", "won"]}, "pouting-face": {"a": "pouting face", "b": "1F621", "j": ["pouting", "rage", "despise", "hate", "face", "red", "angry", "mad"]}, "angry-face": {"a": "angry face", "b": "1F620", "j": ["annoyed", "anger", "frustrated", "face", "angry", "mad"]}, "face-with-symbols-on-mouth": {"a": "face with symbols on mouth", "b": "1F92C", "j": ["cursing", "swearing", "face", "expletive", "cussing", "profanity"]}, "smiling-face-with-horns": {"a": "smiling face with horns", "b": "1F608", "j": ["face", "fairy tale", "smile", "fantasy", "devil", "horns"]}, "angry-face-with-horns": {"a": "angry face with horns", "b": "1F47F", "j": ["face", "devil", "fantasy", "demon", "imp", "horns", "angry"]}, "skull": {"a": "skull", "b": "1F480", "j": ["death", "skeleton", "face", "creepy", "fairy tale", "dead", "monster"]}, "skull-and-crossbones": {"a": "skull and crossbones", "b": "2620", "j": ["danger", "death", "pirate", "skull", "scary", "face", "poison", "evil", "crossbones", "deadly", "monster"]}, "pile-of-poo": {"a": "pile of poo", "b": "1F4A9", "j": ["dung", "poop", "hankey", "face", "shitface", "fail", "turd", "shit", "poo", "monster"]}, "clown-face": {"a": "clown face", "b": "1F921", "j": ["clown", "face"]}, "ogre": {"a": "ogre", "b": "1F479", "j": ["mask", "scary", "japanese", "face", "creepy", "fairy tale", "halloween", "red", "fantasy", "creature", "troll", "devil", "demon", "monster"]}, "goblin": {"a": "goblin", "b": "1F47A", "j": ["mask", "scary", "japanese", "face", "creepy", "fairy tale", "red", "fantasy", "creature", "evil", "monster"]}, "ghost": {"a": "ghost", "b": "1F47B", "j": ["scary", "face", "fairy tale", "halloween", "spooky", "fantasy", "creature", "monster"]}, "alien": {"a": "alien", "b": "1F47D", "j": ["extraterrestrial", "paul", "face", "fantasy", "creature", "ufo", "weird", "outer_space", "UFO"]}, "alien-monster": {"a": "alien monster", "b": "1F47E", "j": ["extraterrestrial", "play", "face", "alien", "game", "creature", "ufo", "arcade", "monster"]}, "robot": {"a": "robot", "b": "1F916", "j": ["computer", "face", "bot", "machine", "monster"]}, "grinning-cat": {"a": "grinning cat", "b": "1F63A", "j": ["grinning", "cats", "open", "face", "smile", "mouth", "happy", "cat", "animal"]}, "grinning-cat-with-smiling-eyes": {"a": "grinning cat with smiling eyes", "b": "1F638", "j": ["animal", "cats", "grin", "face", "smile", "eye", "cat"]}, "cat-with-tears-of-joy": {"a": "cat with tears of joy", "b": "1F639", "j": ["animal", "tears", "cats", "joy", "face", "haha", "happy", "cat", "tear"]}, "smiling-cat-with-hearteyes": {"a": "smiling cat with heart-eyes", "b": "1F63B", "j": ["animal", "love", "like", "cats", "heart", "face", "smile", "eye", "smiling_cat_with_heart_eyes", "valentines", "affection", "cat"]}, "cat-with-wry-smile": {"a": "cat with wry smile", "b": "1F63C", "j": ["animal", "ironic", "cats", "face", "smile", "wry", "cat", "smirk"]}, "kissing-cat": {"a": "kissing cat", "b": "1F63D", "j": ["animal", "cats", "face", "eye", "kiss", "cat"]}, "weary-cat": {"a": "weary cat", "b": "1F640", "j": ["cats", "weary", "munch", "face", "oh", "surprised", "scared", "scream", "cat", "animal"]}, "crying-cat": {"a": "crying cat", "b": "1F63F", "j": ["upset", "animal", "tears", "cats", "weep", "face", "cry", "sad", "cat", "tear"]}, "pouting-cat": {"a": "pouting cat", "b": "1F63E", "j": ["pouting", "cats", "face", "cat", "animal"]}, "seenoevil-monkey": {"a": "see-no-evil monkey", "b": "1F648", "j": ["monkey", "forbidden", "face", "nature", "haha", "evil", "see", "animal", "see_no_evil_monkey"]}, "hearnoevil-monkey": {"a": "hear-no-evil monkey", "b": "1F649", "j": ["monkey", "forbidden", "animal", "hear_no_evil_monkey", "face", "nature", "evil", "hear"]}, "speaknoevil-monkey": {"a": "speak-no-evil monkey", "b": "1F64A", "j": ["monkey", "forbidden", "face", "speak_no_evil_monkey", "nature", "speak", "omg", "evil", "animal"]}, "kiss-mark": {"a": "kiss mark", "b": "1F48B", "j": ["love", "like", "face", "lips", "valentines", "affection", "kiss"]}, "love-letter": {"a": "love letter", "b": "1F48C", "j": ["envelope", "love", "like", "letter", "heart", "email", "valentines", "mail", "affection"]}, "heart-with-arrow": {"a": "heart with arrow", "b": "1F498", "j": ["arrow", "love", "like", "heart", "cupid", "valentines", "affection"]}, "heart-with-ribbon": {"a": "heart with ribbon", "b": "1F49D", "j": ["valentines", "valentine", "love", "ribbon"]}, "sparkling-heart": {"a": "sparkling heart", "b": "1F496", "j": ["love", "like", "excited", "valentines", "sparkle", "affection"]}, "growing-heart": {"a": "growing heart", "b": "1F497", "j": ["pulse", "nervous", "like", "love", "excited", "pink", "valentines", "growing", "affection"]}, "beating-heart": {"a": "beating heart", "b": "1F493", "j": ["love", "like", "heart", "beating", "pink", "heartbeat", "valentines", "pulsating", "affection"]}, "revolving-hearts": {"a": "revolving hearts", "b": "1F49E", "j": ["revolving", "like", "love", "valentines", "affection"]}, "two-hearts": {"a": "two hearts", "b": "1F495", "j": ["love", "like", "heart", "valentines", "affection"]}, "heart-decoration": {"a": "heart decoration", "b": "1F49F", "j": ["heart", "love", "like", "purple-square"]}, "heart-exclamation": {"a": "heart exclamation", "b": "2763", "j": ["punctuation", "love", "mark", "decoration", "exclamation"]}, "broken-heart": {"a": "broken heart", "b": "1F494", "j": ["heartbreak", "heart", "broken", "sorry", "break", "sad"]}, "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", "valentines", "love", "like"]}, "orange-heart": {"a": "orange heart", "b": "1F9E1", "j": ["like", "love", "orange", "valentines", "affection"]}, "yellow-heart": {"a": "yellow heart", "b": "1F49B", "j": ["love", "like", "valentines", "yellow", "affection"]}, "green-heart": {"a": "green heart", "b": "1F49A", "j": ["love", "like", "valentines", "green", "affection"]}, "blue-heart": {"a": "blue heart", "b": "1F499", "j": ["blue", "like", "love", "valentines", "affection"]}, "purple-heart": {"a": "purple heart", "b": "1F49C", "j": ["love", "like", "valentines", "affection", "purple"]}, "brown-heart": {"a": "brown heart", "b": "1F90E", "j": ["heart", "brown", "coffee"]}, "black-heart": {"a": "black heart", "b": "1F5A4", "j": ["wicked", "black", "evil"]}, "white-heart": {"a": "white heart", "b": "1F90D", "j": ["heart", "white", "pure"]}, "hundred-points": {"a": "hundred points", "b": "1F4AF", "j": ["test", "full", "score", "quiz", "numbers", "hundred", "100", "perfect", "pass", "century", "exam"]}, "anger-symbol": {"a": "anger symbol", "b": "1F4A2", "j": ["comic", "mad", "angry"]}, "collision": {"a": "collision", "b": "1F4A5", "j": ["boom", "explode", "explosion", "comic", "bomb", "blown"]}, "dizzy": {"a": "dizzy", "b": "1F4AB", "j": ["magic", "star", "comic", "sparkle", "shoot"]}, "sweat-droplets": {"a": "sweat droplets", "b": "1F4A6", "j": ["oops", "sweat", "drip", "water", "comic", "splashing"]}, "dashing-away": {"a": "dashing away", "b": "1F4A8", "j": ["smoke", "fart", "dash", "puff", "air", "fast", "comic", "running", "shoo", "wind"]}, "hole": {"a": "hole", "b": "1F573", "j": ["embarrassing"]}, "bomb": {"a": "bomb", "b": "1F4A3", "j": ["boom", "explode", "explosion", "comic", "terrorism"]}, "speech-balloon": {"a": "speech balloon", "b": "1F4AC", "j": ["bubble", "balloon", "words", "message", "talk", "chatting", "comic", "speech", "dialog"]}, "eye-in-speech-bubble": {"a": "eye in speech bubble", "b": "1F441-FE0F-200D-1F5E8-FE0F", "j": ["info", "witness", "speech bubble", "eye"]}, "left-speech-bubble": {"a": "left speech bubble", "b": "1F5E8", "j": ["message", "talk", "words", "chatting", "speech", "dialog"]}, "right-anger-bubble": {"a": "right anger bubble", "b": "1F5EF", "j": ["bubble", "balloon", "thinking", "caption", "speech", "angry", "mad"]}, "thought-balloon": {"a": "thought balloon", "b": "1F4AD", "j": ["bubble", "dream", "balloon", "thinking", "comic", "cloud", "speech", "thought"]}, "zzz": {"a": "zzz", "b": "1F4A4", "j": ["sleep", "dream", "tired", "comic", "sleepy"]}, "waving-hand": {"a": "waving hand", "b": "1F44B", "j": ["waving", "wave", "farewell", "solong", "gesture", "hand", "hands", "hello", "goodbye", "hi", "palm"]}, "raised-back-of-hand": {"a": "raised back of hand", "b": "1F91A", "j": ["backhand", "fingers", "raised"]}, "hand-with-fingers-splayed": {"a": "hand with fingers splayed", "b": "1F590", "j": ["finger", "fingers", "hand", "splayed", "palm"]}, "raised-hand": {"a": "raised hand", "b": "270B", "j": ["fingers", "stop", "hand", "high 5", "high five", "ban", "palm", "highfive"]}, "vulcan-salute": {"a": "vulcan salute", "b": "1F596", "j": ["finger", "fingers", "hand", "vulcan", "star trek", "spock"]}, "ok-hand": {"a": "OK hand", "b": "1F44C", "j": ["OK", "limbs", "fingers", "hand", "okay", "perfect", "ok"]}, "pinched-fingers": {"a": "pinched fingers", "b": "1F90C", "j": ["pinched", "fingers", "hand gesture", "size", "tiny", "sarcastic", "small", "interrogation"]}, "pinching-hand": {"a": "pinching hand", "b": "1F90F", "j": ["small amount", "size", "small", "tiny"]}, "victory-hand": {"a": "victory hand", "b": "270C", "j": ["victory", "fingers", "hand", "ohyeah", "two", "v", "peace"]}, "crossed-fingers": {"a": "crossed fingers", "b": "1F91E", "j": ["cross", "finger", "lucky", "good", "hand", "luck"]}, "loveyou-gesture": {"a": "love-you gesture", "b": "1F91F", "j": ["ILY", "fingers", "gesture", "hand", "love_you_gesture"]}, "sign-of-the-horns": {"a": "sign of the horns", "b": "1F918", "j": ["finger", "rock_on", "fingers", "rock-on", "hand", "sign_of_horns", "horns", "evil_eye"]}, "call-me-hand": {"a": "call me hand", "b": "1F919", "j": ["gesture", "hand", "call", "hands"]}, "backhand-index-pointing-left": {"a": "backhand index pointing left", "b": "1F448", "j": ["finger", "fingers", "point", "hand", "left", "backhand", "direction", "index"]}, "backhand-index-pointing-right": {"a": "backhand index pointing right", "b": "1F449", "j": ["finger", "right", "fingers", "point", "hand", "backhand", "direction", "index"]}, "backhand-index-pointing-up": {"a": "backhand index pointing up", "b": "1F446", "j": ["finger", "fingers", "point", "hand", "backhand", "direction", "up"]}, "middle-finger": {"a": "middle finger", "b": "1F595", "j": ["flipping", "finger", "middle", "fingers", "hand", "rude"]}, "backhand-index-pointing-down": {"a": "backhand index pointing down", "b": "1F447", "j": ["finger", "fingers", "point", "down", "hand", "backhand", "direction"]}, "index-pointing-up": {"a": "index pointing up", "b": "261D", "j": ["finger", "fingers", "point", "hand", "direction", "up", "index"]}, "thumbs-up": {"a": "thumbs up", "b": "1F44D", "j": ["+1", "yes", "awesome", "like", "cool", "thumb", "thumbsup", "hand", "good", "agree", "accept", "up"]}, "thumbs-down": {"a": "thumbs down", "b": "1F44E", "j": ["-1", "no", "thumb", "down", "hand", "dislike", "thumbsdown"]}, "raised-fist": {"a": "raised fist", "b": "270A", "j": ["fingers", "clenched", "fist", "hand", "grasp", "punch"]}, "oncoming-fist": {"a": "oncoming fist", "b": "1F44A", "j": ["violence", "clenched", "fist", "hit", "hand", "attack", "punch", "angry"]}, "leftfacing-fist": {"a": "left-facing fist", "b": "1F91B", "j": ["left_facing_fist", "leftwards", "fist", "hand", "fistbump"]}, "rightfacing-fist": {"a": "right-facing fist", "b": "1F91C", "j": ["fist", "hand", "right_facing_fist", "rightwards", "fistbump"]}, "clapping-hands": {"a": "clapping hands", "b": "1F44F", "j": ["applause", "hand", "yay", "hands", "clap", "congrats", "praise"]}, "raising-hands": {"a": "raising hands", "b": "1F64C", "j": ["gesture", "hand", "hands", "celebration", "raised", "hooray", "yea"]}, "open-hands": {"a": "open hands", "b": "1F450", "j": ["fingers", "hand", "open", "hands", "butterfly"]}, "palms-up-together": {"a": "palms up together", "b": "1F932", "j": ["gesture", "cupped", "hands", "cupped hands", "prayer"]}, "handshake": {"a": "handshake", "b": "1F91D", "j": ["shake", "meeting", "hand", "agreement"]}, "folded-hands": {"a": "folded hands", "b": "1F64F", "j": ["hand", "high 5", "please", "high five", "thanks", "wish", "pray", "ask", "namaste", "hope", "highfive"]}, "writing-hand": {"a": "writing hand", "b": "270D", "j": ["lower_left_ballpoint_pen", "compose", "hand", "stationery", "write"]}, "nail-polish": {"a": "nail polish", "b": "1F485", "j": ["care", "finger", "manicure", "fashion", "polish", "beauty", "nail", "cosmetics"]}, "selfie": {"a": "selfie", "b": "1F933", "j": ["camera", "phone"]}, "flexed-biceps": {"a": "flexed biceps", "b": "1F4AA", "j": ["strong", "hand", "arm", "comic", "flex", "biceps", "summer", "muscle"]}, "mechanical-arm": {"a": "mechanical arm", "b": "1F9BE", "j": ["prosthetic", "accessibility"]}, "mechanical-leg": {"a": "mechanical leg", "b": "1F9BF", "j": ["prosthetic", "accessibility"]}, "leg": {"a": "leg", "b": "1F9B5", "j": ["limb", "kick"]}, "foot": {"a": "foot", "b": "1F9B6", "j": ["kick", "stomp"]}, "ear": {"a": "ear", "b": "1F442", "j": ["sound", "face", "body", "hear", "listen"]}, "ear-with-hearing-aid": {"a": "ear with hearing aid", "b": "1F9BB", "j": ["hard of hearing", "accessibility"]}, "nose": {"a": "nose", "b": "1F443", "j": ["smell", "sniff", "body"]}, "brain": {"a": "brain", "b": "1F9E0", "j": ["smart", "intelligent"]}, "anatomical-heart": {"a": "anatomical heart", "b": "1FAC0", "j": ["pulse", "heart", "cardiology", "organ", "heartbeat", "health", "anatomical"]}, "lungs": {"a": "lungs", "b": "1FAC1", "j": ["inhalation", "breath", "exhalation", "organ", "respiration", "breathe"]}, "tooth": {"a": "tooth", "b": "1F9B7", "j": ["dentist", "teeth"]}, "bone": {"a": "bone", "b": "1F9B4", "j": ["skeleton"]}, "eyes": {"a": "eyes", "b": "1F440", "j": ["stalk", "peek", "face", "eye", "watch", "see", "look"]}, "eye": {"a": "eye", "b": "1F441", "j": ["stare", "face", "watch", "body", "see", "look"]}, "tongue": {"a": "tongue", "b": "1F445", "j": ["playful", "mouth", "body"]}, "mouth": {"a": "mouth", "b": "1F444", "j": ["lips", "kiss"]}, "baby": {"a": "baby", "b": "1F476", "j": ["toddler", "young", "boy", "girl", "child"]}, "child": {"a": "child", "b": "1F9D2", "j": ["young", "gender-neutral", "unspecified gender"]}, "boy": {"a": "boy", "b": "1F466", "j": ["guy", "man", "male", "young", "teenager"]}, "girl": {"a": "girl", "b": "1F467", "j": ["woman", "young", "zodiac", "Virgo", "teenager", "female"]}, "person": {"a": "person", "b": "1F9D1", "j": ["adult", "gender-neutral", "unspecified gender"]}, "person-blond-hair": {"a": "person: blond hair", "b": "1F471", "j": ["hairstyle", "blond-haired person", "blond", "hair"]}, "man": {"a": "man", "b": "1F468", "j": ["guy", "classy", "moustache", "father", "sir", "dad", "mustache", "adult"]}, "person-beard": {"a": "person: beard", "b": "1F9D4", "j": ["person", "bewhiskered", "man_beard", "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", "red hair", "man", "hairstyle"]}, "man-curly-hair": {"a": "man: curly hair", "b": "1F468-200D-1F9B1", "j": ["curly hair", "adult", "man", "hairstyle"]}, "man-white-hair": {"a": "man: white hair", "b": "1F468-200D-1F9B3", "j": ["man", "elder", "old", "adult", "white hair"]}, "man-bald": {"a": "man: bald", "b": "1F468-200D-1F9B2", "j": ["hairless", "adult", "man", "bald"]}, "woman": {"a": "woman", "b": "1F469", "j": ["girls", "lady", "adult", "female"]}, "woman-red-hair": {"a": "woman: red hair", "b": "1F469-200D-1F9B0", "j": ["woman", "adult", "red hair", "hairstyle"]}, "person-red-hair": {"a": "person: red hair", "b": "1F9D1-200D-1F9B0", "j": ["person", "unspecified gender", "hairstyle", "red hair", "adult", "gender-neutral"]}, "woman-curly-hair": {"a": "woman: curly hair", "b": "1F469-200D-1F9B1", "j": ["woman", "curly hair", "adult", "hairstyle"]}, "person-curly-hair": {"a": "person: curly hair", "b": "1F9D1-200D-1F9B1", "j": ["person", "unspecified gender", "hairstyle", "curly hair", "adult", "gender-neutral"]}, "woman-white-hair": {"a": "woman: white hair", "b": "1F469-200D-1F9B3", "j": ["woman", "elder", "old", "adult", "white hair"]}, "person-white-hair": {"a": "person: white hair", "b": "1F9D1-200D-1F9B3", "j": ["person", "unspecified gender", "elder", "white hair", "old", "adult", "gender-neutral"]}, "woman-bald": {"a": "woman: bald", "b": "1F469-200D-1F9B2", "j": ["hairless", "adult", "bald", "woman"]}, "person-bald": {"a": "person: bald", "b": "1F9D1-200D-1F9B2", "j": ["hairless", "person", "unspecified gender", "bald", "adult", "gender-neutral"]}, "woman-blond-hair": {"a": "woman: blond hair", "b": "1F471-200D-2640-FE0F", "j": ["person", "woman", "hair", "girl", "blond-haired woman", "blonde", "female"]}, "man-blond-hair": {"a": "man: blond hair", "b": "1F471-200D-2642-FE0F", "j": ["guy", "person", "man", "hair", "male", "boy", "blond-haired man", "blond", "blonde"]}, "older-person": {"a": "older person", "b": "1F9D3", "j": ["unspecified gender", "elder", "senior", "human", "old", "adult", "gender-neutral"]}, "old-man": {"a": "old man", "b": "1F474", "j": ["man", "male", "elder", "human", "senior", "old", "men", "adult"]}, "old-woman": {"a": "old woman", "b": "1F475", "j": ["woman", "elder", "human", "lady", "senior", "old", "adult", "women", "female"]}, "person-frowning": {"a": "person frowning", "b": "1F64D", "j": ["frown", "gesture", "worried"]}, "man-frowning": {"a": "man frowning", "b": "1F64D-200D-2642-FE0F", "j": ["frowning", "depressed", "man", "male", "gesture", "boy", "unhappy", "sad", "discouraged"]}, "woman-frowning": {"a": "woman frowning", "b": "1F64D-200D-2640-FE0F", "j": ["frowning", "depressed", "woman", "gesture", "girl", "unhappy", "sad", "discouraged", "female"]}, "person-pouting": {"a": "person pouting", "b": "1F64E", "j": ["pouting", "upset", "gesture"]}, "man-pouting": {"a": "man pouting", "b": "1F64E-200D-2642-FE0F", "j": ["pouting", "man", "male", "gesture", "boy"]}, "woman-pouting": {"a": "woman pouting", "b": "1F64E-200D-2640-FE0F", "j": ["pouting", "woman", "gesture", "girl", "female"]}, "person-gesturing-no": {"a": "person gesturing NO", "b": "1F645", "j": ["decline", "forbidden", "gesture", "prohibited", "hand"]}, "man-gesturing-no": {"a": "man gesturing NO", "b": "1F645-200D-2642-FE0F", "j": ["forbidden", "man", "male", "gesture", "prohibited", "hand", "boy", "nope"]}, "woman-gesturing-no": {"a": "woman gesturing NO", "b": "1F645-200D-2640-FE0F", "j": ["forbidden", "woman", "gesture", "prohibited", "hand", "girl", "female", "nope"]}, "person-gesturing-ok": {"a": "person gesturing OK", "b": "1F646", "j": ["OK", "hand", "gesture", "agree"]}, "man-gesturing-ok": {"a": "man gesturing OK", "b": "1F646-200D-2642-FE0F", "j": ["blue", "OK", "man", "male", "human", "gesture", "boy", "hand", "men"]}, "woman-gesturing-ok": {"a": "woman gesturing OK", "b": "1F646-200D-2640-FE0F", "j": ["OK", "woman", "human", "gesture", "girl", "hand", "pink", "women", "female"]}, "person-tipping-hand": {"a": "person tipping hand", "b": "1F481", "j": ["tipping", "hand", "sassy", "help", "information"]}, "man-tipping-hand": {"a": "man tipping hand", "b": "1F481-200D-2642-FE0F", "j": ["man", "male", "human", "boy", "sassy", "tipping hand", "information"]}, "woman-tipping-hand": {"a": "woman tipping hand", "b": "1F481-200D-2640-FE0F", "j": ["woman", "human", "girl", "sassy", "tipping hand", "information", "female"]}, "person-raising-hand": {"a": "person raising hand", "b": "1F64B", "j": ["gesture", "hand", "raised", "happy", "question"]}, "man-raising-hand": {"a": "man raising hand", "b": "1F64B-200D-2642-FE0F", "j": ["raising hand", "man", "male", "gesture", "boy"]}, "woman-raising-hand": {"a": "woman raising hand", "b": "1F64B-200D-2640-FE0F", "j": ["raising hand", "woman", "gesture", "girl", "female"]}, "deaf-person": {"a": "deaf person", "b": "1F9CF", "j": ["ear", "deaf", "accessibility", "hear"]}, "deaf-man": {"a": "deaf man", "b": "1F9CF-200D-2642-FE0F", "j": ["accessibility", "deaf", "man"]}, "deaf-woman": {"a": "deaf woman", "b": "1F9CF-200D-2640-FE0F", "j": ["deaf", "accessibility", "woman"]}, "person-bowing": {"a": "person bowing", "b": "1F647", "j": ["gesture", "sorry", "apology", "respectiful", "bow"]}, "man-bowing": {"a": "man bowing", "b": "1F647-200D-2642-FE0F", "j": ["man", "male", "gesture", "boy", "bowing", "sorry", "favor", "apology"]}, "woman-bowing": {"a": "woman bowing", "b": "1F647-200D-2640-FE0F", "j": ["woman", "gesture", "girl", "bowing", "sorry", "favor", "apology", "female"]}, "person-facepalming": {"a": "person facepalming", "b": "1F926", "j": ["disappointed", "disbelief", "face", "exasperation", "palm"]}, "man-facepalming": {"a": "man facepalming", "b": "1F926-200D-2642-FE0F", "j": ["disbelief", "facepalm", "man", "male", "boy", "exasperation"]}, "woman-facepalming": {"a": "woman facepalming", "b": "1F926-200D-2640-FE0F", "j": ["disbelief", "facepalm", "woman", "girl", "exasperation", "female"]}, "person-shrugging": {"a": "person shrugging", "b": "1F937", "j": ["regardless", "indifference", "doubt", "shrug", "ignorance"]}, "man-shrugging": {"a": "man shrugging", "b": "1F937-200D-2642-FE0F", "j": ["indifferent", "man", "male", "boy", "doubt", "indifference", "confused", "shrug", "ignorance"]}, "woman-shrugging": {"a": "woman shrugging", "b": "1F937-200D-2640-FE0F", "j": ["indifferent", "woman", "girl", "doubt", "indifference", "confused", "female", "shrug", "ignorance"]}, "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", "human", "doctor", "healthcare", "therapist"]}, "woman-health-worker": {"a": "woman health worker", "b": "1F469-200D-2695-FE0F", "j": ["nurse", "woman", "human", "doctor", "healthcare", "therapist"]}, "student": {"a": "student", "b": "1F9D1-200D-1F393", "j": ["learn", "graduate"]}, "man-student": {"a": "man student", "b": "1F468-200D-1F393", "j": ["human", "graduate", "student", "man"]}, "woman-student": {"a": "woman student", "b": "1F469-200D-1F393", "j": ["human", "graduate", "student", "woman"]}, "teacher": {"a": "teacher", "b": "1F9D1-200D-1F3EB", "j": ["professor", "instructor"]}, "man-teacher": {"a": "man teacher", "b": "1F468-200D-1F3EB", "j": ["instructor", "man", "teacher", "human", "professor"]}, "woman-teacher": {"a": "woman teacher", "b": "1F469-200D-1F3EB", "j": ["instructor", "woman", "teacher", "human", "professor"]}, "judge": {"a": "judge", "b": "1F9D1-200D-2696-FE0F", "j": ["scales", "justice", "law"]}, "man-judge": {"a": "man judge", "b": "1F468-200D-2696-FE0F", "j": ["court", "man", "human", "scales", "judge", "justice"]}, "woman-judge": {"a": "woman judge", "b": "1F469-200D-2696-FE0F", "j": ["court", "woman", "human", "scales", "judge", "justice"]}, "farmer": {"a": "farmer", "b": "1F9D1-200D-1F33E", "j": ["rancher", "crops", "gardener"]}, "man-farmer": {"a": "man farmer", "b": "1F468-200D-1F33E", "j": ["farmer", "man", "human", "rancher", "gardener"]}, "woman-farmer": {"a": "woman farmer", "b": "1F469-200D-1F33E", "j": ["farmer", "woman", "human", "rancher", "gardener"]}, "cook": {"a": "cook", "b": "1F9D1-200D-1F373", "j": ["chef", "food", "culinary", "kitchen"]}, "man-cook": {"a": "man cook", "b": "1F468-200D-1F373", "j": ["human", "cook", "chef", "man"]}, "woman-cook": {"a": "woman cook", "b": "1F469-200D-1F373", "j": ["human", "cook", "chef", "woman"]}, "mechanic": {"a": "mechanic", "b": "1F9D1-200D-1F527", "j": ["worker", "electrician", "plumber", "technician", "tradesperson"]}, "man-mechanic": {"a": "man mechanic", "b": "1F468-200D-1F527", "j": ["man", "human", "electrician", "plumber", "tradesperson", "mechanic", "wrench"]}, "woman-mechanic": {"a": "woman mechanic", "b": "1F469-200D-1F527", "j": ["woman", "human", "electrician", "plumber", "tradesperson", "mechanic", "wrench"]}, "factory-worker": {"a": "factory worker", "b": "1F9D1-200D-1F3ED", "j": ["worker", "factory", "labor", "industrial", "assembly"]}, "man-factory-worker": {"a": "man factory worker", "b": "1F468-200D-1F3ED", "j": ["man", "worker", "human", "factory", "industrial", "assembly"]}, "woman-factory-worker": {"a": "woman factory worker", "b": "1F469-200D-1F3ED", "j": ["worker", "woman", "human", "factory", "industrial", "assembly"]}, "office-worker": {"a": "office worker", "b": "1F9D1-200D-1F4BC", "j": ["white-collar", "architect", "manager", "business"]}, "man-office-worker": {"a": "man office worker", "b": "1F468-200D-1F4BC", "j": ["white-collar", "architect", "man", "business", "manager", "human"]}, "woman-office-worker": {"a": "woman office worker", "b": "1F469-200D-1F4BC", "j": ["white-collar", "architect", "manager", "business", "woman", "human"]}, "scientist": {"a": "scientist", "b": "1F9D1-200D-1F52C", "j": ["chemistry", "biologist", "physicist", "engineer", "chemist"]}, "man-scientist": {"a": "man scientist", "b": "1F468-200D-1F52C", "j": ["man", "biologist", "human", "physicist", "scientist", "engineer", "chemist"]}, "woman-scientist": {"a": "woman scientist", "b": "1F469-200D-1F52C", "j": ["biologist", "woman", "human", "physicist", "scientist", "engineer", "chemist"]}, "technologist": {"a": "technologist", "b": "1F9D1-200D-1F4BB", "j": ["computer", "software", "coder", "developer", "inventor"]}, "man-technologist": {"a": "man technologist", "b": "1F468-200D-1F4BB", "j": ["programmer", "laptop", "software", "computer", "coder", "man", "human", "developer", "inventor", "engineer", "technologist"]}, "woman-technologist": {"a": "woman technologist", "b": "1F469-200D-1F4BB", "j": ["programmer", "laptop", "software", "computer", "coder", "woman", "human", "developer", "inventor", "engineer", "technologist"]}, "singer": {"a": "singer", "b": "1F9D1-200D-1F3A4", "j": ["song", "artist", "star", "rock", "actor", "entertainer", "performer"]}, "man-singer": {"a": "man singer", "b": "1F468-200D-1F3A4", "j": ["man", "human", "star", "rockstar", "rock", "singer", "actor", "entertainer"]}, "woman-singer": {"a": "woman singer", "b": "1F469-200D-1F3A4", "j": ["woman", "human", "star", "rockstar", "rock", "singer", "actor", "entertainer"]}, "artist": {"a": "artist", "b": "1F9D1-200D-1F3A8", "j": ["painting", "creativity", "palette", "draw"]}, "man-artist": {"a": "man artist", "b": "1F468-200D-1F3A8", "j": ["man", "artist", "human", "painter", "palette"]}, "woman-artist": {"a": "woman artist", "b": "1F469-200D-1F3A8", "j": ["woman", "artist", "human", "painter", "palette"]}, "pilot": {"a": "pilot", "b": "1F9D1-200D-2708-FE0F", "j": ["airplane", "plane", "fly"]}, "man-pilot": {"a": "man pilot", "b": "1F468-200D-2708-FE0F", "j": ["man", "human", "plane", "pilot", "aviator"]}, "woman-pilot": {"a": "woman pilot", "b": "1F469-200D-2708-FE0F", "j": ["woman", "human", "plane", "pilot", "aviator"]}, "astronaut": {"a": "astronaut", "b": "1F9D1-200D-1F680", "j": ["outerspace", "rocket"]}, "man-astronaut": {"a": "man astronaut", "b": "1F468-200D-1F680", "j": ["man", "rocket", "human", "astronaut", "space"]}, "woman-astronaut": {"a": "woman astronaut", "b": "1F469-200D-1F680", "j": ["rocket", "woman", "human", "astronaut", "space"]}, "firefighter": {"a": "firefighter", "b": "1F9D1-200D-1F692", "j": ["firetruck", "fire"]}, "man-firefighter": {"a": "man firefighter", "b": "1F468-200D-1F692", "j": ["firetruck", "man", "human", "fireman", "firefighter"]}, "woman-firefighter": {"a": "woman firefighter", "b": "1F469-200D-1F692", "j": ["firetruck", "woman", "human", "fireman", "firefighter"]}, "police-officer": {"a": "police officer", "b": "1F46E", "j": ["cop", "police", "officer"]}, "man-police-officer": {"a": "man police officer", "b": "1F46E-200D-2642-FE0F", "j": ["police", "man", "officer", "law", "enforcement", "911", "legal", "cop", "arrest"]}, "woman-police-officer": {"a": "woman police officer", "b": "1F46E-200D-2640-FE0F", "j": ["police", "woman", "officer", "law", "enforcement", "arrest", "911", "legal", "cop", "female"]}, "detective": {"a": "detective", "b": "1F575", "j": ["sleuth", "human", "spy"]}, "man-detective": {"a": "man detective", "b": "1F575-FE0F-200D-2642-FE0F", "j": ["spy", "man", "crime", "sleuth", "detective"]}, "woman-detective": {"a": "woman detective", "b": "1F575-FE0F-200D-2640-FE0F", "j": ["spy", "woman", "human", "sleuth", "female", "detective"]}, "guard": {"a": "guard", "b": "1F482", "j": ["protect"]}, "man-guard": {"a": "man guard", "b": "1F482-200D-2642-FE0F", "j": ["guy", "royal", "man", "british", "male", "uk", "gb", "guard"]}, "woman-guard": {"a": "woman guard", "b": "1F482-200D-2640-FE0F", "j": ["royal", "woman", "british", "uk", "gb", "guard", "female"]}, "ninja": {"a": "ninja", "b": "1F977", "j": ["ninjutsu", "japanese", "fighter", "hidden", "stealth", "skills"]}, "construction-worker": {"a": "construction worker", "b": "1F477", "j": ["build", "construction", "worker", "hat", "labor"]}, "man-construction-worker": {"a": "man construction worker", "b": "1F477-200D-2642-FE0F", "j": ["guy", "man", "construction", "male", "worker", "human", "wip", "build", "labor"]}, "woman-construction-worker": {"a": "woman construction worker", "b": "1F477-200D-2640-FE0F", "j": ["build", "construction", "worker", "woman", "human", "wip", "labor", "female"]}, "prince": {"a": "prince", "b": "1F934", "j": ["royal", "man", "male", "crown", "king", "boy"]}, "princess": {"a": "princess", "b": "1F478", "j": ["royal", "woman", "crown", "girl", "fairy tale", "fantasy", "blond", "queen", "female"]}, "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", "male", "hinduism", "turban", "indian", "arabs"]}, "woman-wearing-turban": {"a": "woman wearing turban", "b": "1F473-200D-2640-FE0F", "j": ["woman", "hinduism", "turban", "indian", "arabs", "female"]}, "person-with-skullcap": {"a": "person with skullcap", "b": "1F472", "j": ["person", "man_with_skullcap", "male", "boy", "hat", "skullcap", "cap", "gua pi mao", "chinese"]}, "woman-with-headscarf": {"a": "woman with headscarf", "b": "1F9D5", "j": ["tichel", "mantilla", "bandana", "head kerchief", "headscarf", "female", "hijab"]}, "person-in-tuxedo": {"a": "person in tuxedo", "b": "1F935", "j": ["person", "wedding", "marriage", "tuxedo", "groom", "man_in_tuxedo", "couple"]}, "man-in-tuxedo": {"a": "man in tuxedo", "b": "1F935-200D-2642-FE0F", "j": ["formal", "fashion", "man", "tuxedo"]}, "woman-in-tuxedo": {"a": "woman in tuxedo", "b": "1F935-200D-2640-FE0F", "j": ["formal", "fashion", "tuxedo", "woman"]}, "person-with-veil": {"a": "person with veil", "b": "1F470", "j": ["person", "wedding", "marriage", "woman", "bride", "veil", "bride_with_veil", "couple"]}, "man-with-veil": {"a": "man with veil", "b": "1F470-200D-2642-FE0F", "j": ["veil", "wedding", "man", "marriage"]}, "woman-with-veil": {"a": "woman with veil", "b": "1F470-200D-2640-FE0F", "j": ["veil", "wedding", "marriage", "woman"]}, "pregnant-woman": {"a": "pregnant woman", "b": "1F930", "j": ["pregnant", "baby", "woman"]}, "breastfeeding": {"a": "breast-feeding", "b": "1F931", "j": ["nursing", "breast", "baby", "breast_feeding"]}, "woman-feeding-baby": {"a": "woman feeding baby", "b": "1F469-200D-1F37C", "j": ["nursing", "birth", "woman", "baby", "feeding", "food"]}, "man-feeding-baby": {"a": "man feeding baby", "b": "1F468-200D-1F37C", "j": ["nursing", "birth", "man", "baby", "feeding", "food"]}, "person-feeding-baby": {"a": "person feeding baby", "b": "1F9D1-200D-1F37C", "j": ["nursing", "person", "birth", "baby", "feeding", "food"]}, "baby-angel": {"a": "baby angel", "b": "1F47C", "j": ["heaven", "baby", "face", "fairy tale", "fantasy", "wings", "angel", "halo"]}, "santa-claus": {"a": "Santa Claus", "b": "1F385", "j": ["claus", "man", "male", "father", "santa", "festival", "xmas", "celebration", "Christmas", "father christmas"]}, "mrs-claus": {"a": "Mrs. Claus", "b": "1F936", "j": ["claus", "Mrs.", "woman", "xmas", "celebration", "Christmas", "mother christmas", "mother", "female"]}, "mx-claus": {"a": "mx claus", "b": "1F9D1-200D-1F384", "j": ["Claus, christmas", "christmas"]}, "superhero": {"a": "superhero", "b": "1F9B8", "j": ["marvel", "superpower", "good", "heroine", "hero"]}, "man-superhero": {"a": "man superhero", "b": "1F9B8-200D-2642-FE0F", "j": ["man", "male", "superpowers", "superpower", "good", "hero"]}, "woman-superhero": {"a": "woman superhero", "b": "1F9B8-200D-2640-FE0F", "j": ["woman", "superpowers", "good", "superpower", "heroine", "hero", "female"]}, "supervillain": {"a": "supervillain", "b": "1F9B9", "j": ["marvel", "superpower", "criminal", "villain", "evil"]}, "man-supervillain": {"a": "man supervillain", "b": "1F9B9-200D-2642-FE0F", "j": ["man", "male", "superpowers", "superpower", "criminal", "bad", "villain", "evil", "hero"]}, "woman-supervillain": {"a": "woman supervillain", "b": "1F9B9-200D-2640-FE0F", "j": ["woman", "superpowers", "superpower", "criminal", "bad", "heroine", "villain", "evil", "female"]}, "mage": {"a": "mage", "b": "1F9D9", "j": ["magic", "wizard", "sorcerer", "sorceress", "witch"]}, "man-mage": {"a": "man mage", "b": "1F9D9-200D-2642-FE0F", "j": ["man", "male", "wizard", "sorcerer", "mage"]}, "woman-mage": {"a": "woman mage", "b": "1F9D9-200D-2640-FE0F", "j": ["woman", "mage", "sorceress", "witch", "female"]}, "fairy": {"a": "fairy", "b": "1F9DA", "j": ["magical", "Puck", "Titania", "Oberon", "wings"]}, "man-fairy": {"a": "man fairy", "b": "1F9DA-200D-2642-FE0F", "j": ["Puck", "Oberon", "man", "male"]}, "woman-fairy": {"a": "woman fairy", "b": "1F9DA-200D-2640-FE0F", "j": ["Titania", "woman", "female"]}, "vampire": {"a": "vampire", "b": "1F9DB", "j": ["undead", "blood", "twilight", "Dracula"]}, "man-vampire": {"a": "man vampire", "b": "1F9DB-200D-2642-FE0F", "j": ["dracula", "man", "male", "undead", "Dracula"]}, "woman-vampire": {"a": "woman vampire", "b": "1F9DB-200D-2640-FE0F", "j": ["woman", "undead", "female"]}, "merperson": {"a": "merperson", "b": "1F9DC", "j": ["mermaid", "merwoman", "sea", "merman"]}, "merman": {"a": "merman", "b": "1F9DC-200D-2642-FE0F", "j": ["triton", "Triton", "man", "male"]}, "mermaid": {"a": "mermaid", "b": "1F9DC-200D-2640-FE0F", "j": ["woman", "merwoman", "ariel", "female"]}, "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": ["woman", "magical", "female"]}, "genie": {"a": "genie", "b": "1F9DE", "j": ["(non-human color)", "magical", "wishes", "djinn"]}, "man-genie": {"a": "man genie", "b": "1F9DE-200D-2642-FE0F", "j": ["male", "man", "djinn"]}, "woman-genie": {"a": "woman genie", "b": "1F9DE-200D-2640-FE0F", "j": ["female", "djinn", "woman"]}, "zombie": {"a": "zombie", "b": "1F9DF", "j": ["undead", "dead", "walking dead", "(non-human color)"]}, "man-zombie": {"a": "man zombie", "b": "1F9DF-200D-2642-FE0F", "j": ["dracula", "man", "male", "undead", "walking dead"]}, "woman-zombie": {"a": "woman zombie", "b": "1F9DF-200D-2640-FE0F", "j": ["woman", "undead", "walking dead", "female"]}, "person-getting-massage": {"a": "person getting massage", "b": "1F486", "j": ["relax", "salon", "massage", "face"]}, "man-getting-massage": {"a": "man getting massage", "b": "1F486-200D-2642-FE0F", "j": ["man", "massage", "male", "boy", "face", "head"]}, "woman-getting-massage": {"a": "woman getting massage", "b": "1F486-200D-2640-FE0F", "j": ["massage", "woman", "girl", "face", "head", "female"]}, "person-getting-haircut": {"a": "person getting haircut", "b": "1F487", "j": ["barber", "haircut", "hairstyle", "beauty", "parlor"]}, "man-getting-haircut": {"a": "man getting haircut", "b": "1F487-200D-2642-FE0F", "j": ["boy", "male", "man", "haircut"]}, "woman-getting-haircut": {"a": "woman getting haircut", "b": "1F487-200D-2640-FE0F", "j": ["woman", "girl", "haircut", "female"]}, "person-walking": {"a": "person walking", "b": "1F6B6", "j": ["move", "walking", "walk", "hike"]}, "man-walking": {"a": "man walking", "b": "1F6B6-200D-2642-FE0F", "j": ["man", "human", "steps", "feet", "hike", "walk"]}, "woman-walking": {"a": "woman walking", "b": "1F6B6-200D-2640-FE0F", "j": ["woman", "human", "steps", "feet", "hike", "walk", "female"]}, "person-standing": {"a": "person standing", "b": "1F9CD", "j": ["stand", "standing", "still"]}, "man-standing": {"a": "man standing", "b": "1F9CD-200D-2642-FE0F", "j": ["standing", "man", "still"]}, "woman-standing": {"a": "woman standing", "b": "1F9CD-200D-2640-FE0F", "j": ["standing", "still", "woman"]}, "person-kneeling": {"a": "person kneeling", "b": "1F9CE", "j": ["respectful", "pray", "kneeling", "kneel"]}, "man-kneeling": {"a": "man kneeling", "b": "1F9CE-200D-2642-FE0F", "j": ["respectful", "pray", "man", "kneeling"]}, "woman-kneeling": {"a": "woman kneeling", "b": "1F9CE-200D-2640-FE0F", "j": ["respectful", "pray", "kneeling", "woman"]}, "person-with-white-cane": {"a": "person with white cane", "b": "1F9D1-200D-1F9AF", "j": ["person_with_probing_cane", "blind", "accessibility"]}, "man-with-white-cane": {"a": "man with white cane", "b": "1F468-200D-1F9AF", "j": ["man", "blind", "accessibility", "man_with_probing_cane"]}, "woman-with-white-cane": {"a": "woman with white cane", "b": "1F469-200D-1F9AF", "j": ["woman_with_probing_cane", "blind", "accessibility", "woman"]}, "person-in-motorized-wheelchair": {"a": "person in motorized wheelchair", "b": "1F9D1-200D-1F9BC", "j": ["wheelchair", "disability", "accessibility"]}, "man-in-motorized-wheelchair": {"a": "man in motorized wheelchair", "b": "1F468-200D-1F9BC", "j": ["wheelchair", "disability", "man", "accessibility"]}, "woman-in-motorized-wheelchair": {"a": "woman in motorized wheelchair", "b": "1F469-200D-1F9BC", "j": ["wheelchair", "disability", "accessibility", "woman"]}, "person-in-manual-wheelchair": {"a": "person in manual wheelchair", "b": "1F9D1-200D-1F9BD", "j": ["wheelchair", "disability", "accessibility"]}, "man-in-manual-wheelchair": {"a": "man in manual wheelchair", "b": "1F468-200D-1F9BD", "j": ["wheelchair", "disability", "man", "accessibility"]}, "woman-in-manual-wheelchair": {"a": "woman in manual wheelchair", "b": "1F469-200D-1F9BD", "j": ["wheelchair", "disability", "accessibility", "woman"]}, "person-running": {"a": "person running", "b": "1F3C3", "j": ["running", "marathon", "move"]}, "man-running": {"a": "man running", "b": "1F3C3-200D-2642-FE0F", "j": ["man", "walking", "racing", "race", "running", "exercise", "marathon"]}, "woman-running": {"a": "woman running", "b": "1F3C3-200D-2640-FE0F", "j": ["woman", "walking", "racing", "race", "running", "exercise", "marathon", "female"]}, "woman-dancing": {"a": "woman dancing", "b": "1F483", "j": ["woman", "girl", "dance", "dancing", "fun", "female"]}, "man-dancing": {"a": "man dancing", "b": "1F57A", "j": ["man", "male", "boy", "dance", "dancing", "fun", "dancer"]}, "person-in-suit-levitating": {"a": "person in suit levitating", "b": "1F574", "j": ["person", "business", "jump", "man_in_suit_levitating", "suit", "hover", "levitate"]}, "people-with-bunny-ears": {"a": "people with bunny ears", "b": "1F46F", "j": ["costume", "partying", "perform", "bunny ear", "dancer"]}, "men-with-bunny-ears": {"a": "men with bunny ears", "b": "1F46F-200D-2642-FE0F", "j": ["boys", "male", "partying", "men", "bunny", "bunny ear", "dancer"]}, "women-with-bunny-ears": {"a": "women with bunny ears", "b": "1F46F-200D-2640-FE0F", "j": ["girls", "dancer", "partying", "bunny", "bunny ear", "women", "female"]}, "person-in-steamy-room": {"a": "person in steamy room", "b": "1F9D6", "j": ["spa", "relax", "hamam", "steambath", "sauna", "steam room"]}, "man-in-steamy-room": {"a": "man in steamy room", "b": "1F9D6-200D-2642-FE0F", "j": ["man", "male", "steamroom", "spa", "sauna", "steam room"]}, "woman-in-steamy-room": {"a": "woman in steamy room", "b": "1F9D6-200D-2640-FE0F", "j": ["woman", "steamroom", "spa", "sauna", "steam room", "female"]}, "person-climbing": {"a": "person climbing", "b": "1F9D7", "j": ["sport", "climber"]}, "man-climbing": {"a": "man climbing", "b": "1F9D7-200D-2642-FE0F", "j": ["man", "hobby", "male", "rock", "sports", "climber"]}, "woman-climbing": {"a": "woman climbing", "b": "1F9D7-200D-2640-FE0F", "j": ["hobby", "woman", "rock", "sports", "climber", "female"]}, "person-fencing": {"a": "person fencing", "b": "1F93A", "j": ["sword", "fencer", "fencing", "sports"]}, "horse-racing": {"a": "horse racing", "b": "1F3C7", "j": ["competition", "racehorse", "jockey", "racing", "gambling", "luck", "horse", "betting", "animal"]}, "skier": {"a": "skier", "b": "26F7", "j": ["sports", "snow", "winter", "ski"]}, "snowboarder": {"a": "snowboarder", "b": "1F3C2", "j": ["snow", "winter", "sports", "ski", "snowboard"]}, "person-golfing": {"a": "person golfing", "b": "1F3CC", "j": ["golf", "sports", "business", "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", "woman", "golf", "sports", "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", "sea", "sports", "ocean", "summer", "beach"]}, "woman-surfing": {"a": "woman surfing", "b": "1F3C4-200D-2640-FE0F", "j": ["woman", "surfing", "sea", "sports", "ocean", "summer", "beach", "female"]}, "person-rowing-boat": {"a": "person rowing boat", "b": "1F6A3", "j": ["sport", "rowboat", "move", "boat"]}, "man-rowing-boat": {"a": "man rowing boat", "b": "1F6A3-200D-2642-FE0F", "j": ["man", "hobby", "boat", "water", "sports", "ship", "rowboat"]}, "woman-rowing-boat": {"a": "woman rowing boat", "b": "1F6A3-200D-2640-FE0F", "j": ["hobby", "woman", "boat", "water", "sports", "ship", "rowboat", "female"]}, "person-swimming": {"a": "person swimming", "b": "1F3CA", "j": ["sport", "pool", "swim"]}, "man-swimming": {"a": "man swimming", "b": "1F3CA-200D-2642-FE0F", "j": ["swim", "man", "human", "athlete", "water", "sports", "exercise", "summer"]}, "woman-swimming": {"a": "woman swimming", "b": "1F3CA-200D-2640-FE0F", "j": ["swim", "woman", "human", "athlete", "water", "sports", "exercise", "summer", "female"]}, "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": ["sport", "man", "ball"]}, "woman-bouncing-ball": {"a": "woman bouncing ball", "b": "26F9-FE0F-200D-2640-FE0F", "j": ["woman", "human", "ball", "sports", "female"]}, "person-lifting-weights": {"a": "person lifting weights", "b": "1F3CB", "j": ["sports", "training", "exercise", "weight", "lifter"]}, "man-lifting-weights": {"a": "man lifting weights", "b": "1F3CB-FE0F-200D-2642-FE0F", "j": ["sport", "weight lifter", "man"]}, "woman-lifting-weights": {"a": "woman lifting weights", "b": "1F3CB-FE0F-200D-2640-FE0F", "j": ["woman", "sports", "exercise", "weight lifter", "training", "female"]}, "person-biking": {"a": "person biking", "b": "1F6B4", "j": ["biking", "sport", "bicycle", "move", "cyclist"]}, "man-biking": {"a": "man biking", "b": "1F6B4-200D-2642-FE0F", "j": ["biking", "man", "hipster", "sports", "exercise", "bicycle", "bike", "cyclist"]}, "woman-biking": {"a": "woman biking", "b": "1F6B4-200D-2640-FE0F", "j": ["biking", "woman", "cyclist", "hipster", "sports", "exercise", "bicycle", "bike", "female"]}, "person-mountain-biking": {"a": "person mountain biking", "b": "1F6B5", "j": ["mountain", "bicyclist", "sport", "bicycle", "bike", "move", "cyclist"]}, "man-mountain-biking": {"a": "man mountain biking", "b": "1F6B5-200D-2642-FE0F", "j": ["mountain", "man", "human", "transportation", "sports", "race", "bicycle", "bike", "cyclist"]}, "woman-mountain-biking": {"a": "woman mountain biking", "b": "1F6B5-200D-2640-FE0F", "j": ["biking", "mountain", "woman", "human", "cyclist", "transportation", "sports", "race", "bicycle", "bike", "female"]}, "person-cartwheeling": {"a": "person cartwheeling", "b": "1F938", "j": ["sport", "cartwheel", "gymnastic", "gymnastics"]}, "man-cartwheeling": {"a": "man cartwheeling", "b": "1F938-200D-2642-FE0F", "j": ["cartwheel", "man", "gymnastics"]}, "woman-cartwheeling": {"a": "woman cartwheeling", "b": "1F938-200D-2640-FE0F", "j": ["cartwheel", "gymnastics", "woman"]}, "people-wrestling": {"a": "people wrestling", "b": "1F93C", "j": ["wrestle", "sport", "wrestler"]}, "men-wrestling": {"a": "men wrestling", "b": "1F93C-200D-2642-FE0F", "j": ["wrestle", "wrestlers", "sports", "men"]}, "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", "polo", "water"]}, "man-playing-water-polo": {"a": "man playing water polo", "b": "1F93D-200D-2642-FE0F", "j": ["pool", "water polo", "man", "sports"]}, "woman-playing-water-polo": {"a": "woman playing water polo", "b": "1F93D-200D-2640-FE0F", "j": ["woman", "pool", "water polo", "sports"]}, "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": ["handball", "man", "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": ["performance", "balance", "skill", "multitask", "juggle"]}, "man-juggling": {"a": "man juggling", "b": "1F939-200D-2642-FE0F", "j": ["juggling", "balance", "man", "skill", "multitask", "juggle"]}, "woman-juggling": {"a": "woman juggling", "b": "1F939-200D-2640-FE0F", "j": ["juggling", "balance", "skill", "woman", "multitask", "juggle"]}, "person-in-lotus-position": {"a": "person in lotus position", "b": "1F9D8", "j": ["meditation", "meditate", "serenity", "yoga"]}, "man-in-lotus-position": {"a": "man in lotus position", "b": "1F9D8-200D-2642-FE0F", "j": ["man", "male", "meditation", "mindfulness", "yoga", "zen", "serenity"]}, "woman-in-lotus-position": {"a": "woman in lotus position", "b": "1F9D8-200D-2640-FE0F", "j": ["woman", "meditation", "mindfulness", "yoga", "zen", "serenity", "female"]}, "person-taking-bath": {"a": "person taking bath", "b": "1F6C0", "j": ["bathroom", "bath", "clean", "shower", "bathtub"]}, "person-in-bed": {"a": "person in bed", "b": "1F6CC", "j": ["sleep", "rest", "hotel", "bed"]}, "people-holding-hands": {"a": "people holding hands", "b": "1F9D1-200D-1F91D-200D-1F9D1", "j": ["person", "friendship", "hold", "hand", "holding hands", "couple"]}, "women-holding-hands": {"a": "women holding hands", "b": "1F46D", "j": ["friendship", "people", "love", "like", "human", "hand", "holding hands", "women", "couple", "pair", "female"]}, "woman-and-man-holding-hands": {"a": "woman and man holding hands", "b": "1F46B", "j": ["love", "dating", "like", "woman", "marriage", "human", "hand", "valentines", "affection", "couple", "pair", "date", "people", "man", "hold", "holding hands"]}, "men-holding-hands": {"a": "men holding hands", "b": "1F46C", "j": ["friendship", "people", "love", "man", "twins", "like", "human", "bromance", "holding hands", "zodiac", "Gemini", "men", "couple", "pair"]}, "kiss": {"a": "kiss", "b": "1F48F", "j": ["love", "dating", "like", "marriage", "valentines", "couple", "pair"]}, "kiss-woman-man": {"a": "kiss: woman, man", "b": "1F469-200D-2764-FE0F-200D-1F48B-200D-1F468", "j": ["love", "man", "woman", "kiss", "couple"]}, "kiss-man-man": {"a": "kiss: man, man", "b": "1F468-200D-2764-FE0F-200D-1F48B-200D-1F468", "j": ["love", "man", "like", "dating", "marriage", "valentines", "kiss", "couple", "pair"]}, "kiss-woman-woman": {"a": "kiss: woman, woman", "b": "1F469-200D-2764-FE0F-200D-1F48B-200D-1F469", "j": ["love", "like", "dating", "woman", "marriage", "valentines", "kiss", "couple", "pair"]}, "couple-with-heart": {"a": "couple with heart", "b": "1F491", "j": ["love", "like", "dating", "marriage", "human", "valentines", "affection", "couple", "pair"]}, "couple-with-heart-woman-man": {"a": "couple with heart: woman, man", "b": "1F469-200D-2764-FE0F-200D-1F468", "j": ["love", "man", "woman", "couple with heart", "couple"]}, "couple-with-heart-man-man": {"a": "couple with heart: man, man", "b": "1F468-200D-2764-FE0F-200D-1F468", "j": ["love", "man", "like", "dating", "marriage", "human", "couple with heart", "valentines", "affection", "couple", "pair"]}, "couple-with-heart-woman-woman": {"a": "couple with heart: woman, woman", "b": "1F469-200D-2764-FE0F-200D-1F469", "j": ["love", "like", "dating", "woman", "marriage", "human", "couple with heart", "valentines", "affection", "couple", "pair"]}, "family": {"a": "family", "b": "1F46A", "j": ["people", "mom", "father", "human", "parents", "dad", "home", "child", "mother"]}, "family-man-woman-boy": {"a": "family: man, woman, boy", "b": "1F468-200D-1F469-200D-1F466", "j": ["love", "man", "woman", "boy", "family"]}, "family-man-woman-girl": {"a": "family: man, woman, girl", "b": "1F468-200D-1F469-200D-1F467", "j": ["people", "man", "woman", "human", "girl", "parents", "family", "home", "child"]}, "family-man-woman-girl-boy": {"a": "family: man, woman, girl, boy", "b": "1F468-200D-1F469-200D-1F467-200D-1F466", "j": ["people", "man", "woman", "human", "boy", "girl", "parents", "family", "home", "children"]}, "family-man-woman-boy-boy": {"a": "family: man, woman, boy, boy", "b": "1F468-200D-1F469-200D-1F466-200D-1F466", "j": ["people", "man", "woman", "human", "boy", "parents", "family", "home", "children"]}, "family-man-woman-girl-girl": {"a": "family: man, woman, girl, girl", "b": "1F468-200D-1F469-200D-1F467-200D-1F467", "j": ["people", "man", "woman", "human", "girl", "parents", "family", "home", "children"]}, "family-man-man-boy": {"a": "family: man, man, boy", "b": "1F468-200D-1F468-200D-1F466", "j": ["people", "man", "human", "boy", "parents", "family", "home", "children"]}, "family-man-man-girl": {"a": "family: man, man, girl", "b": "1F468-200D-1F468-200D-1F467", "j": ["people", "man", "human", "girl", "parents", "family", "home", "children"]}, "family-man-man-girl-boy": {"a": "family: man, man, girl, boy", "b": "1F468-200D-1F468-200D-1F467-200D-1F466", "j": ["people", "man", "human", "boy", "girl", "parents", "family", "home", "children"]}, "family-man-man-boy-boy": {"a": "family: man, man, boy, boy", "b": "1F468-200D-1F468-200D-1F466-200D-1F466", "j": ["people", "man", "human", "boy", "parents", "family", "home", "children"]}, "family-man-man-girl-girl": {"a": "family: man, man, girl, girl", "b": "1F468-200D-1F468-200D-1F467-200D-1F467", "j": ["people", "man", "human", "girl", "parents", "family", "home", "children"]}, "family-woman-woman-boy": {"a": "family: woman, woman, boy", "b": "1F469-200D-1F469-200D-1F466", "j": ["people", "woman", "human", "boy", "parents", "family", "home", "children"]}, "family-woman-woman-girl": {"a": "family: woman, woman, girl", "b": "1F469-200D-1F469-200D-1F467", "j": ["people", "woman", "human", "girl", "parents", "family", "home", "children"]}, "family-woman-woman-girl-boy": {"a": "family: woman, woman, girl, boy", "b": "1F469-200D-1F469-200D-1F467-200D-1F466", "j": ["people", "woman", "human", "boy", "girl", "parents", "family", "home", "children"]}, "family-woman-woman-boy-boy": {"a": "family: woman, woman, boy, boy", "b": "1F469-200D-1F469-200D-1F466-200D-1F466", "j": ["people", "woman", "human", "boy", "parents", "family", "home", "children"]}, "family-woman-woman-girl-girl": {"a": "family: woman, woman, girl, girl", "b": "1F469-200D-1F469-200D-1F467-200D-1F467", "j": ["people", "woman", "human", "girl", "parents", "family", "home", "children"]}, "family-man-boy": {"a": "family: man, boy", "b": "1F468-200D-1F466", "j": ["people", "man", "human", "boy", "parent", "family", "home", "child"]}, "family-man-boy-boy": {"a": "family: man, boy, boy", "b": "1F468-200D-1F466-200D-1F466", "j": ["people", "man", "human", "boy", "parent", "family", "home", "children"]}, "family-man-girl": {"a": "family: man, girl", "b": "1F468-200D-1F467", "j": ["people", "man", "human", "girl", "parent", "family", "home", "child"]}, "family-man-girl-boy": {"a": "family: man, girl, boy", "b": "1F468-200D-1F467-200D-1F466", "j": ["people", "man", "human", "boy", "girl", "parent", "family", "home", "children"]}, "family-man-girl-girl": {"a": "family: man, girl, girl", "b": "1F468-200D-1F467-200D-1F467", "j": ["people", "man", "human", "girl", "parent", "family", "home", "children"]}, "family-woman-boy": {"a": "family: woman, boy", "b": "1F469-200D-1F466", "j": ["people", "woman", "human", "boy", "parent", "family", "home", "child"]}, "family-woman-boy-boy": {"a": "family: woman, boy, boy", "b": "1F469-200D-1F466-200D-1F466", "j": ["people", "woman", "human", "boy", "parent", "family", "home", "children"]}, "family-woman-girl": {"a": "family: woman, girl", "b": "1F469-200D-1F467", "j": ["people", "woman", "human", "girl", "parent", "family", "home", "child"]}, "family-woman-girl-boy": {"a": "family: woman, girl, boy", "b": "1F469-200D-1F467-200D-1F466", "j": ["people", "woman", "human", "boy", "girl", "parent", "family", "home", "children"]}, "family-woman-girl-girl": {"a": "family: woman, girl, girl", "b": "1F469-200D-1F467-200D-1F467", "j": ["people", "woman", "human", "girl", "parent", "family", "home", "children"]}, "speaking-head": {"a": "speaking head", "b": "1F5E3", "j": ["person", "talk", "human", "sing", "face", "silhouette", "speak", "say", "speaking", "user", "head"]}, "bust-in-silhouette": {"a": "bust in silhouette", "b": "1F464", "j": ["person", "bust", "human", "silhouette", "user"]}, "busts-in-silhouette": {"a": "busts in silhouette", "b": "1F465", "j": ["team", "bust", "person", "group", "human", "silhouette", "user"]}, "people-hugging": {"a": "people hugging", "b": "1FAC2", "j": ["hug", "hello", "thanks", "goodbye", "care"]}, "footprints": {"a": "footprints", "b": "1F463", "j": ["tracking", "footprint", "walking", "clothing", "feet", "print", "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": ["monkey", "face", "nature", "circus", "animal"]}, "monkey": {"a": "monkey", "b": "1F412", "j": ["banana", "circus", "animal", "nature"]}, "gorilla": {"a": "gorilla", "b": "1F98D", "j": ["circus", "animal", "nature"]}, "orangutan": {"a": "orangutan", "b": "1F9A7", "j": ["animal", "ape"]}, "dog-face": {"a": "dog face", "b": "1F436", "j": ["puppy", "pet", "dog", "face", "nature", "woof", "faithful", "friend", "animal"]}, "dog": {"a": "dog", "b": "1F415", "j": ["pet", "nature", "faithful", "friend", "doge", "animal"]}, "guide-dog": {"a": "guide dog", "b": "1F9AE", "j": ["guide", "animal", "blind", "accessibility"]}, "service-dog": {"a": "service dog", "b": "1F415-200D-1F9BA", "j": ["animal", "accessibility", "dog", "assistance", "service", "blind"]}, "poodle": {"a": "poodle", "b": "1F429", "j": ["pet", "dog", "nature", "101", "animal"]}, "wolf": {"a": "wolf", "b": "1F43A", "j": ["animal", "wild", "face", "nature"]}, "fox": {"a": "fox", "b": "1F98A", "j": ["animal", "face", "nature"]}, "raccoon": {"a": "raccoon", "b": "1F99D", "j": ["curious", "sly", "animal", "nature"]}, "cat-face": {"a": "cat face", "b": "1F431", "j": ["pet", "meow", "kitten", "face", "nature", "cat", "animal"]}, "cat": {"a": "cat", "b": "1F408", "j": ["meow", "pet", "animal", "cats"]}, "black-cat": {"a": "black cat", "b": "1F408-200D-2B1B", "j": ["unlucky", "superstition", "luck", "cat", "black"]}, "lion": {"a": "lion", "b": "1F981", "j": ["zodiac", "Leo", "face", "nature", "animal"]}, "tiger-face": {"a": "tiger face", "b": "1F42F", "j": ["danger", "tiger", "face", "nature", "wild", "cat", "animal", "roar"]}, "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", "face", "nature", "horse", "animal"]}, "horse": {"a": "horse", "b": "1F40E", "j": ["equestrian", "gamble", "racing", "luck", "racehorse", "animal"]}, "unicorn": {"a": "unicorn", "b": "1F984", "j": ["mystical", "animal", "face", "nature"]}, "zebra": {"a": "zebra", "b": "1F993", "j": ["nature", "safari", "stripe", "stripes", "animal"]}, "deer": {"a": "deer", "b": "1F98C", "j": ["venison", "animal", "horns", "nature"]}, "bison": {"a": "bison", "b": "1F9AC", "j": ["wisent", "buffalo", "herd", "ox"]}, "cow-face": {"a": "cow face", "b": "1F42E", "j": ["cow", "milk", "beef", "ox", "face", "nature", "moo", "animal"]}, "ox": {"a": "ox", "b": "1F402", "j": ["cow", "Taurus", "beef", "zodiac", "bull", "animal"]}, "water-buffalo": {"a": "water buffalo", "b": "1F403", "j": ["cow", "buffalo", "water", "ox", "nature", "animal"]}, "cow": {"a": "cow", "b": "1F404", "j": ["milk", "beef", "ox", "nature", "moo", "animal"]}, "pig-face": {"a": "pig face", "b": "1F437", "j": ["pig", "face", "nature", "oink", "animal"]}, "pig": {"a": "pig", "b": "1F416", "j": ["nature", "animal", "sow"]}, "boar": {"a": "boar", "b": "1F417", "j": ["animal", "pig", "nature"]}, "pig-nose": {"a": "pig nose", "b": "1F43D", "j": ["pig", "face", "oink", "nose", "animal"]}, "ram": {"a": "ram", "b": "1F40F", "j": ["male", "zodiac", "nature", "sheep", "Aries", "animal"]}, "ewe": {"a": "ewe", "b": "1F411", "j": ["nature", "sheep", "shipit", "wool", "animal", "female"]}, "goat": {"a": "goat", "b": "1F410", "j": ["animal", "Capricorn", "zodiac", "nature"]}, "camel": {"a": "camel", "b": "1F42A", "j": ["hump", "dromedary", "hot", "desert", "animal"]}, "twohump-camel": {"a": "two-hump camel", "b": "1F42B", "j": ["animal", "hump", "bactrian", "hot", "desert", "nature", "camel", "two_hump_camel"]}, "llama": {"a": "llama", "b": "1F999", "j": ["guanaco", "vicuña", "alpaca", "nature", "wool", "animal"]}, "giraffe": {"a": "giraffe", "b": "1F992", "j": ["spots", "animal", "safari", "nature"]}, "elephant": {"a": "elephant", "b": "1F418", "j": ["nature", "nose", "circus", "animal", "th"]}, "mammoth": {"a": "mammoth", "b": "1F9A3", "j": ["large", "woolly", "elephant", "extinction", "tusks", "tusk"]}, "rhinoceros": {"a": "rhinoceros", "b": "1F98F", "j": ["nature", "animal", "horn"]}, "hippopotamus": {"a": "hippopotamus", "b": "1F99B", "j": ["nature", "animal", "hippo"]}, "mouse-face": {"a": "mouse face", "b": "1F42D", "j": ["mouse", "face", "nature", "animal", "cheese_wedge", "rodent"]}, "mouse": {"a": "mouse", "b": "1F401", "j": ["rodent", "animal", "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": ["pet", "spring", "face", "nature", "rabbit", "bunny", "animal", "magic"]}, "rabbit": {"a": "rabbit", "b": "1F407", "j": ["pet", "spring", "nature", "bunny", "animal", "magic"]}, "chipmunk": {"a": "chipmunk", "b": "1F43F", "j": ["nature", "animal", "squirrel", "rodent"]}, "beaver": {"a": "beaver", "b": "1F9AB", "j": ["animal", "dam", "rodent"]}, "hedgehog": {"a": "hedgehog", "b": "1F994", "j": ["spiny", "animal", "nature"]}, "bat": {"a": "bat", "b": "1F987", "j": ["blind", "animal", "vampire", "nature"]}, "bear": {"a": "bear", "b": "1F43B", "j": ["animal", "wild", "face", "nature"]}, "polar-bear": {"a": "polar bear", "b": "1F43B-200D-2744-FE0F", "j": ["animal", "white", "arctic", "bear"]}, "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", "animal", "lazy"]}, "otter": {"a": "otter", "b": "1F9A6", "j": ["playful", "animal", "fishing"]}, "skunk": {"a": "skunk", "b": "1F9A8", "j": ["stink", "animal"]}, "kangaroo": {"a": "kangaroo", "b": "1F998", "j": ["Australia", "australia", "hop", "joey", "nature", "marsupial", "animal", "jump"]}, "badger": {"a": "badger", "b": "1F9A1", "j": ["nature", "pester", "honey badger", "honey", "animal"]}, "paw-prints": {"a": "paw prints", "b": "1F43E", "j": ["pet", "tracking", "dog", "feet", "paw", "cat", "print", "animal", "footprints"]}, "turkey": {"a": "turkey", "b": "1F983", "j": ["animal", "bird"]}, "chicken": {"a": "chicken", "b": "1F414", "j": ["cluck", "animal", "bird", "nature"]}, "rooster": {"a": "rooster", "b": "1F413", "j": ["chicken", "animal", "bird", "nature"]}, "hatching-chick": {"a": "hatching chick", "b": "1F423", "j": ["born", "hatching", "baby", "bird", "chicken", "chick", "egg", "animal"]}, "baby-chick": {"a": "baby chick", "b": "1F424", "j": ["baby", "bird", "chicken", "chick", "animal"]}, "frontfacing-baby-chick": {"a": "front-facing baby chick", "b": "1F425", "j": ["baby", "bird", "chicken", "front_facing_baby_chick", "chick", "animal"]}, "bird": {"a": "bird", "b": "1F426", "j": ["tweet", "spring", "nature", "fly", "animal"]}, "penguin": {"a": "penguin", "b": "1F427", "j": ["animal", "bird", "nature"]}, "dove": {"a": "dove", "b": "1F54A", "j": ["fly", "animal", "bird", "peace"]}, "eagle": {"a": "eagle", "b": "1F985", "j": ["animal", "bird", "nature"]}, "duck": {"a": "duck", "b": "1F986", "j": ["animal", "bird", "mallard", "nature"]}, "swan": {"a": "swan", "b": "1F9A2", "j": ["ugly duckling", "cygnet", "bird", "nature", "animal"]}, "owl": {"a": "owl", "b": "1F989", "j": ["bird", "wise", "nature", "hoot", "animal"]}, "dodo": {"a": "dodo", "b": "1F9A4", "j": ["large", "bird", "Mauritius", "extinction", "animal"]}, "feather": {"a": "feather", "b": "1FAB6", "j": ["flight", "bird", "plumage", "light", "fly"]}, "flamingo": {"a": "flamingo", "b": "1F9A9", "j": ["tropical", "flamboyant", "animal"]}, "peacock": {"a": "peacock", "b": "1F99A", "j": ["peahen", "bird", "proud", "nature", "ostentatious", "animal"]}, "parrot": {"a": "parrot", "b": "1F99C", "j": ["talk", "pirate", "bird", "nature", "animal"]}, "frog": {"a": "frog", "b": "1F438", "j": ["croak", "face", "toad", "nature", "animal"]}, "crocodile": {"a": "crocodile", "b": "1F40A", "j": ["reptile", "alligator", "nature", "animal", "lizard"]}, "turtle": {"a": "turtle", "b": "1F422", "j": ["slow", "nature", "tortoise", "terrapin", "animal"]}, "lizard": {"a": "lizard", "b": "1F98E", "j": ["reptile", "animal", "nature"]}, "snake": {"a": "snake", "b": "1F40D", "j": ["serpent", "Ophiuchus", "zodiac", "nature", "hiss", "bearer", "evil", "animal", "python"]}, "dragon-face": {"a": "dragon face", "b": "1F432", "j": ["face", "myth", "fairy tale", "nature", "dragon", "chinese", "green", "animal"]}, "dragon": {"a": "dragon", "b": "1F409", "j": ["myth", "nature", "fairy tale", "chinese", "green", "animal"]}, "sauropod": {"a": "sauropod", "b": "1F995", "j": ["diplodocus", "brontosaurus", "nature", "dinosaur", "extinct", "animal", "brachiosaurus"]}, "trex": {"a": "T-Rex", "b": "1F996", "j": ["nature", "Tyrannosaurus Rex", "dinosaur", "t_rex", "tyrannosaurus", "extinct", "animal"]}, "spouting-whale": {"a": "spouting whale", "b": "1F433", "j": ["spouting", "whale", "sea", "face", "nature", "ocean", "animal"]}, "whale": {"a": "whale", "b": "1F40B", "j": ["ocean", "animal", "sea", "nature"]}, "dolphin": {"a": "dolphin", "b": "1F42C", "j": ["sea", "fish", "nature", "flipper", "ocean", "animal", "fins", "beach"]}, "seal": {"a": "seal", "b": "1F9AD", "j": ["sea", "sea lion", "animal", "creature"]}, "fish": {"a": "fish", "b": "1F41F", "j": ["zodiac", "nature", "Pisces", "food", "animal"]}, "tropical-fish": {"a": "tropical fish", "b": "1F420", "j": ["swim", "fish", "ocean", "nemo", "tropical", "animal", "beach"]}, "blowfish": {"a": "blowfish", "b": "1F421", "j": ["sea", "fish", "nature", "ocean", "food", "animal"]}, "shark": {"a": "shark", "b": "1F988", "j": ["jaws", "sea", "fish", "nature", "ocean", "animal", "fins", "beach"]}, "octopus": {"a": "octopus", "b": "1F419", "j": ["sea", "nature", "ocean", "creature", "animal", "beach"]}, "spiral-shell": {"a": "spiral shell", "b": "1F41A", "j": ["sea", "shell", "nature", "spiral", "beach"]}, "snail": {"a": "snail", "b": "1F40C", "j": ["slow", "animal", "shell"]}, "butterfly": {"a": "butterfly", "b": "1F98B", "j": ["caterpillar", "nature", "pretty", "insect", "animal"]}, "bug": {"a": "bug", "b": "1F41B", "j": ["worm", "insect", "animal", "nature"]}, "ant": {"a": "ant", "b": "1F41C", "j": ["bug", "insect", "animal", "nature"]}, "honeybee": {"a": "honeybee", "b": "1F41D", "j": ["spring", "nature", "bug", "bee", "honey", "insect", "animal"]}, "beetle": {"a": "beetle", "b": "1FAB2", "j": ["insect", "bug"]}, "lady-beetle": {"a": "lady beetle", "b": "1F41E", "j": ["beetle", "nature", "ladybird", "ladybug", "insect", "animal"]}, "cricket": {"a": "cricket", "b": "1F997", "j": ["chirp", "animal", "Orthoptera", "grasshopper"]}, "cockroach": {"a": "cockroach", "b": "1FAB3", "j": ["pest", "pests", "insect", "roach"]}, "spider": {"a": "spider", "b": "1F577", "j": ["arachnid", "animal", "insect"]}, "spider-web": {"a": "spider web", "b": "1F578", "j": ["silk", "spider", "arachnid", "insect", "animal", "web"]}, "scorpion": {"a": "scorpion", "b": "1F982", "j": ["Scorpio", "zodiac", "scorpio", "arachnid", "animal"]}, "mosquito": {"a": "mosquito", "b": "1F99F", "j": ["malaria", "pest", "nature", "disease", "fever", "insect", "animal", "virus"]}, "fly": {"a": "fly", "b": "1FAB0", "j": ["pest", "rotting", "disease", "maggot", "insect"]}, "worm": {"a": "worm", "b": "1FAB1", "j": ["earthworm", "animal", "annelid", "parasite"]}, "microbe": {"a": "microbe", "b": "1F9A0", "j": ["bacteria", "germs", "amoeba", "virus"]}, "bouquet": {"a": "bouquet", "b": "1F490", "j": ["flowers", "flower", "spring", "nature"]}, "cherry-blossom": {"a": "cherry blossom", "b": "1F338", "j": ["flower", "spring", "nature", "plant", "cherry", "blossom"]}, "white-flower": {"a": "white flower", "b": "1F4AE", "j": ["flower", "japanese", "spring"]}, "rosette": {"a": "rosette", "b": "1F3F5", "j": ["military", "plant", "decoration", "flower"]}, "rose": {"a": "rose", "b": "1F339", "j": ["flower", "love", "spring", "valentines", "flowers"]}, "wilted-flower": {"a": "wilted flower", "b": "1F940", "j": ["flower", "plant", "wilted", "nature"]}, "hibiscus": {"a": "hibiscus", "b": "1F33A", "j": ["flower", "plant", "flowers", "vegetable", "beach"]}, "sunflower": {"a": "sunflower", "b": "1F33B", "j": ["sun", "flower", "nature", "fall", "plant"]}, "blossom": {"a": "blossom", "b": "1F33C", "j": ["flowers", "flower", "yellow", "nature"]}, "tulip": {"a": "tulip", "b": "1F337", "j": ["flower", "spring", "nature", "plant", "summer", "flowers"]}, "seedling": {"a": "seedling", "b": "1F331", "j": ["spring", "young", "lawn", "nature", "plant", "grass"]}, "potted-plant": {"a": "potted plant", "b": "1FAB4", "j": ["house", "nurturing", "greenery", "grow", "plant", "useless", "boring"]}, "evergreen-tree": {"a": "evergreen tree", "b": "1F332", "j": ["tree", "plant", "nature"]}, "deciduous-tree": {"a": "deciduous tree", "b": "1F333", "j": ["shedding", "deciduous", "nature", "plant", "tree"]}, "palm-tree": {"a": "palm tree", "b": "1F334", "j": ["nature", "plant", "tree", "mojito", "summer", "tropical", "vegetable", "palm", "beach"]}, "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": ["weed", "leaf", "lawn", "medicine", "plant", "vegetable", "grass"]}, "shamrock": {"a": "shamrock", "b": "2618", "j": ["clover", "nature", "plant", "irish", "vegetable"]}, "four-leaf-clover": {"a": "four leaf clover", "b": "1F340", "j": ["four-leaf clover", "leaf", "lucky", "four", "clover", "nature", "plant", "4", "irish", "vegetable"]}, "maple-leaf": {"a": "maple leaf", "b": "1F341", "j": ["leaf", "maple", "ca", "nature", "fall", "plant", "falling", "vegetable"]}, "fallen-leaf": {"a": "fallen leaf", "b": "1F342", "j": ["leaf", "nature", "plant", "falling", "leaves", "vegetable"]}, "leaf-fluttering-in-wind": {"a": "leaf fluttering in wind", "b": "1F343", "j": ["flutter", "leaf", "spring", "lawn", "blow", "nature", "plant", "tree", "vegetable", "wind", "grass"]}, "grapes": {"a": "grapes", "b": "1F347", "j": ["wine", "grape", "food", "fruit"]}, "melon": {"a": "melon", "b": "1F348", "j": ["nature", "food", "fruit"]}, "watermelon": {"a": "watermelon", "b": "1F349", "j": ["fruit", "food", "picnic", "summer"]}, "tangerine": {"a": "tangerine", "b": "1F34A", "j": ["fruit", "food", "orange", "nature"]}, "lemon": {"a": "lemon", "b": "1F34B", "j": ["citrus", "nature", "fruit"]}, "banana": {"a": "banana", "b": "1F34C", "j": ["monkey", "food", "fruit"]}, "pineapple": {"a": "pineapple", "b": "1F34D", "j": ["nature", "food", "fruit"]}, "mango": {"a": "mango", "b": "1F96D", "j": ["tropical", "food", "fruit"]}, "red-apple": {"a": "red apple", "b": "1F34E", "j": ["apple", "fruit", "school", "mac", "red"]}, "green-apple": {"a": "green apple", "b": "1F34F", "j": ["fruit", "apple", "green", "nature"]}, "pear": {"a": "pear", "b": "1F350", "j": ["nature", "food", "fruit"]}, "peach": {"a": "peach", "b": "1F351", "j": ["nature", "food", "fruit"]}, "cherries": {"a": "cherries", "b": "1F352", "j": ["fruit", "red", "cherry", "food", "berries"]}, "strawberry": {"a": "strawberry", "b": "1F353", "j": ["fruit", "food", "berry", "nature"]}, "blueberries": {"a": "blueberries", "b": "1FAD0", "j": ["blue", "blueberry", "berry", "bilberry", "fruit"]}, "kiwi-fruit": {"a": "kiwi fruit", "b": "1F95D", "j": ["kiwi", "food", "fruit"]}, "tomato": {"a": "tomato", "b": "1F345", "j": ["fruit", "food", "vegetable", "nature"]}, "olive": {"a": "olive", "b": "1FAD2", "j": ["food", "fruit"]}, "coconut": {"a": "coconut", "b": "1F965", "j": ["fruit", "piña colada", "nature", "food", "palm"]}, "avocado": {"a": "avocado", "b": "1F951", "j": ["food", "fruit"]}, "eggplant": {"a": "eggplant", "b": "1F346", "j": ["aubergine", "food", "vegetable", "nature"]}, "potato": {"a": "potato", "b": "1F954", "j": ["tuber", "starch", "vegatable", "food", "vegetable"]}, "carrot": {"a": "carrot", "b": "1F955", "j": ["food", "vegetable", "orange"]}, "ear-of-corn": {"a": "ear of corn", "b": "1F33D", "j": ["corn", "maze", "ear", "food", "plant", "maize", "vegetable"]}, "hot-pepper": {"a": "hot pepper", "b": "1F336", "j": ["chili", "hot", "spicy", "pepper", "food", "chilli"]}, "bell-pepper": {"a": "bell pepper", "b": "1FAD1", "j": ["fruit", "pepper", "capsicum", "plant", "vegetable"]}, "cucumber": {"a": "cucumber", "b": "1F952", "j": ["pickle", "food", "vegetable", "fruit"]}, "leafy-green": {"a": "leafy green", "b": "1F96C", "j": ["cabbage", "kale", "bok choy", "plant", "food", "lettuce", "vegetable"]}, "broccoli": {"a": "broccoli", "b": "1F966", "j": ["wild cabbage", "food", "vegetable", "fruit"]}, "garlic": {"a": "garlic", "b": "1F9C4", "j": ["cook", "food", "flavoring", "spice"]}, "onion": {"a": "onion", "b": "1F9C5", "j": ["cook", "food", "flavoring", "spice"]}, "mushroom": {"a": "mushroom", "b": "1F344", "j": ["plant", "vegetable", "toadstool"]}, "peanuts": {"a": "peanuts", "b": "1F95C", "j": ["nut", "food", "vegetable", "peanut"]}, "chestnut": {"a": "chestnut", "b": "1F330", "j": ["plant", "food", "squirrel"]}, "bread": {"a": "bread", "b": "1F35E", "j": ["loaf", "toast", "breakfast", "food", "wheat"]}, "croissant": {"a": "croissant", "b": "1F950", "j": ["french", "bread", "breakfast", "food", "roll"]}, "baguette-bread": {"a": "baguette bread", "b": "1F956", "j": ["french", "baguette", "food", "bread"]}, "flatbread": {"a": "flatbread", "b": "1FAD3", "j": ["arepa", "lavash", "naan", "pita", "food", "flour"]}, "pretzel": {"a": "pretzel", "b": "1F968", "j": ["convoluted", "food", "bread", "twisted"]}, "bagel": {"a": "bagel", "b": "1F96F", "j": ["bread", "bakery", "breakfast", "food", "schmear"]}, "pancakes": {"a": "pancakes", "b": "1F95E", "j": ["hotcakes", "hotcake", "pancake", "flapjacks", "breakfast", "food", "crêpe"]}, "waffle": {"a": "waffle", "b": "1F9C7", "j": ["iron", "food", "breakfast", "indecisive"]}, "cheese-wedge": {"a": "cheese wedge", "b": "1F9C0", "j": ["chadder", "food", "cheese"]}, "meat-on-bone": {"a": "meat on bone", "b": "1F356", "j": ["drumstick", "bone", "good", "meat", "food"]}, "poultry-leg": {"a": "poultry leg", "b": "1F357", "j": ["leg", "drumstick", "bone", "poultry", "bird", "meat", "chicken", "food", "turkey"]}, "cut-of-meat": {"a": "cut of meat", "b": "1F969", "j": ["cow", "steak", "meat", "food", "lambchop", "chop", "porkchop", "cut"]}, "bacon": {"a": "bacon", "b": "1F953", "j": ["pig", "meat", "breakfast", "food", "pork"]}, "hamburger": {"a": "hamburger", "b": "1F354", "j": ["burger king", "fast food", "beef", "meat", "burger", "mcdonalds", "cheeseburger"]}, "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": ["food", "hotdog", "sausage", "frankfurter"]}, "sandwich": {"a": "sandwich", "b": "1F96A", "j": ["lunch", "food", "bread"]}, "taco": {"a": "taco", "b": "1F32E", "j": ["food", "mexican"]}, "burrito": {"a": "burrito", "b": "1F32F", "j": ["food", "mexican", "wrap"]}, "tamale": {"a": "tamale", "b": "1FAD4", "j": ["masa", "food", "wrapped", "mexican"]}, "stuffed-flatbread": {"a": "stuffed flatbread", "b": "1F959", "j": ["kebab", "flatbread", "food", "stuffed", "gyro", "falafel"]}, "falafel": {"a": "falafel", "b": "1F9C6", "j": ["chickpea", "food", "meatball"]}, "egg": {"a": "egg", "b": "1F95A", "j": ["breakfast", "food", "chicken"]}, "cooking": {"a": "cooking", "b": "1F373", "j": ["breakfast", "food", "frying", "kitchen", "egg", "pan"]}, "shallow-pan-of-food": {"a": "shallow pan of food", "b": "1F958", "j": ["cooking", "shallow", "paella", "food", "pan", "casserole"]}, "pot-of-food": {"a": "pot of food", "b": "1F372", "j": ["pot", "stew", "soup", "meat", "food"]}, "fondue": {"a": "fondue", "b": "1FAD5", "j": ["pot", "melted", "cheese", "chocolate", "food", "Swiss"]}, "bowl-with-spoon": {"a": "bowl with spoon", "b": "1F963", "j": ["porridge", "breakfast", "food", "cereal", "congee", "oatmeal"]}, "green-salad": {"a": "green salad", "b": "1F957", "j": ["salad", "healthy", "food", "green", "lettuce"]}, "popcorn": {"a": "popcorn", "b": "1F37F", "j": ["snack", "food", "films", "movie theater"]}, "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": ["food", "box", "japanese", "bento"]}, "rice-cracker": {"a": "rice cracker", "b": "1F358", "j": ["rice", "food", "japanese", "cracker"]}, "rice-ball": {"a": "rice ball", "b": "1F359", "j": ["rice", "Japanese", "japanese", "ball", "food"]}, "cooked-rice": {"a": "cooked rice", "b": "1F35A", "j": ["china", "rice", "asian", "food", "cooked"]}, "curry-rice": {"a": "curry rice", "b": "1F35B", "j": ["curry", "rice", "hot", "spicy", "food", "indian"]}, "steaming-bowl": {"a": "steaming bowl", "b": "1F35C", "j": ["bowl", "japanese", "steaming", "food", "noodle", "ramen", "chopsticks"]}, "spaghetti": {"a": "spaghetti", "b": "1F35D", "j": ["italian", "food", "noodle", "pasta"]}, "roasted-sweet-potato": {"a": "roasted sweet potato", "b": "1F360", "j": ["roasted", "potato", "nature", "sweet", "food"]}, "oden": {"a": "oden", "b": "1F362", "j": ["stick", "kebab", "skewer", "japanese", "food", "seafood"]}, "sushi": {"a": "sushi", "b": "1F363", "j": ["rice", "food", "japanese", "fish"]}, "fried-shrimp": {"a": "fried shrimp", "b": "1F364", "j": ["prawn", "shrimp", "appetizer", "fried", "tempura", "food", "summer", "animal"]}, "fish-cake-with-swirl": {"a": "fish cake with swirl", "b": "1F365", "j": ["beach", "pink", "narutomaki", "sea", "fish", "surimi", "pastry", "cake", "food", "ramen", "swirl", "kamaboko", "japan"]}, "moon-cake": {"a": "moon cake", "b": "1F96E", "j": ["food", "yuèbǐng", "festival", "autumn"]}, "dango": {"a": "dango", "b": "1F361", "j": ["stick", "dessert", "Japanese", "japanese", "meat", "sweet", "barbecue", "food", "skewer"]}, "dumpling": {"a": "dumpling", "b": "1F95F", "j": ["jiaozi", "food", "pierogi", "empanada", "gyōza", "potsticker"]}, "fortune-cookie": {"a": "fortune cookie", "b": "1F960", "j": ["food", "prophecy"]}, "takeout-box": {"a": "takeout box", "b": "1F961", "j": ["oyster pail", "leftovers", "food"]}, "crab": {"a": "crab", "b": "1F980", "j": ["crustacean", "animal", "zodiac", "Cancer"]}, "lobster": {"a": "lobster", "b": "1F99E", "j": ["claws", "bisque", "nature", "animal", "seafood"]}, "shrimp": {"a": "shrimp", "b": "1F990", "j": ["shellfish", "nature", "ocean", "food", "small", "animal", "seafood"]}, "squid": {"a": "squid", "b": "1F991", "j": ["sea", "nature", "ocean", "food", "animal", "molusc"]}, "oyster": {"a": "oyster", "b": "1F9AA", "j": ["food", "pearl", "diving"]}, "soft-ice-cream": {"a": "soft ice cream", "b": "1F366", "j": ["hot", "dessert", "ice", "soft", "icecream", "sweet", "food", "summer", "cream"]}, "shaved-ice": {"a": "shaved ice", "b": "1F367", "j": ["hot", "dessert", "ice", "sweet", "summer", "shaved"]}, "ice-cream": {"a": "ice cream", "b": "1F368", "j": ["hot", "dessert", "ice", "sweet", "food", "cream"]}, "doughnut": {"a": "doughnut", "b": "1F369", "j": ["dessert", "sweet", "breakfast", "food", "snack", "donut"]}, "cookie": {"a": "cookie", "b": "1F36A", "j": ["dessert", "sweet", "chocolate", "food", "snack", "oreo"]}, "birthday-cake": {"a": "birthday cake", "b": "1F382", "j": ["birthday", "dessert", "pastry", "celebration", "sweet", "cake", "food"]}, "shortcake": {"a": "shortcake", "b": "1F370", "j": ["dessert", "pastry", "sweet", "cake", "food", "slice"]}, "cupcake": {"a": "cupcake", "b": "1F9C1", "j": ["sweet", "bakery", "dessert", "food"]}, "pie": {"a": "pie", "b": "1F967", "j": ["fruit", "dessert", "meat", "filling", "pastry", "food"]}, "chocolate-bar": {"a": "chocolate bar", "b": "1F36B", "j": ["dessert", "bar", "sweet", "chocolate", "food", "snack"]}, "candy": {"a": "candy", "b": "1F36C", "j": ["sweet", "snack", "dessert", "lolly"]}, "lollipop": {"a": "lollipop", "b": "1F36D", "j": ["dessert", "sweet", "candy", "food", "snack"]}, "custard": {"a": "custard", "b": "1F36E", "j": ["sweet", "dessert", "food", "pudding"]}, "honey-pot": {"a": "honey pot", "b": "1F36F", "j": ["pot", "kitchen", "bees", "sweet", "honeypot", "honey"]}, "baby-bottle": {"a": "baby bottle", "b": "1F37C", "j": ["milk", "drink", "bottle", "baby", "food", "container"]}, "glass-of-milk": {"a": "glass of milk", "b": "1F95B", "j": ["cow", "milk", "drink", "beverage", "glass"]}, "hot-beverage": {"a": "hot beverage", "b": "2615", "j": ["hot", "beverage", "drink", "steaming", "coffee", "caffeine", "tea", "latte", "espresso"]}, "teapot": {"a": "teapot", "b": "1FAD6", "j": ["hot", "pot", "drink", "tea"]}, "teacup-without-handle": {"a": "teacup without handle", "b": "1F375", "j": ["bowl", "cup", "british", "beverage", "teacup", "drink", "breakfast", "tea", "green"]}, "sake": {"a": "sake", "b": "1F376", "j": ["cup", "wine", "beverage", "drink", "drunk", "bottle", "bar", "japanese", "alcohol", "booze"]}, "bottle-with-popping-cork": {"a": "bottle with popping cork", "b": "1F37E", "j": ["wine", "drink", "bottle", "bar", "celebration", "popping", "cork"]}, "wine-glass": {"a": "wine glass", "b": "1F377", "j": ["wine", "beverage", "drink", "drunk", "alcohol", "bar", "booze", "glass"]}, "cocktail-glass": {"a": "cocktail glass", "b": "1F378", "j": ["drink", "alcohol", "drunk", "beverage", "bar", "cocktail", "mojito", "booze", "glass"]}, "tropical-drink": {"a": "tropical drink", "b": "1F379", "j": ["drink", "beverage", "alcohol", "bar", "cocktail", "mojito", "summer", "tropical", "booze", "beach"]}, "beer-mug": {"a": "beer mug", "b": "1F37A", "j": ["pub", "drink", "beverage", "drunk", "alcohol", "bar", "relax", "mug", "party", "summer", "booze", "beer"]}, "clinking-beer-mugs": {"a": "clinking beer mugs", "b": "1F37B", "j": ["pub", "drink", "beverage", "drunk", "alcohol", "clink", "bar", "relax", "mug", "party", "summer", "booze", "beer"]}, "clinking-glasses": {"a": "clinking glasses", "b": "1F942", "j": ["cheers", "toast", "champagne", "wine", "drink", "beverage", "alcohol", "clink", "party", "glass", "celebrate"]}, "tumbler-glass": {"a": "tumbler glass", "b": "1F943", "j": ["bourbon", "shot", "drink", "beverage", "drunk", "alcohol", "scotch", "liquor", "tumbler", "whisky", "booze", "glass"]}, "cup-with-straw": {"a": "cup with straw", "b": "1F964", "j": ["malt", "drink", "water", "soft drink", "soda", "juice"]}, "bubble-tea": {"a": "bubble tea", "b": "1F9CB", "j": ["bubble", "boba", "tea", "milk", "pearl", "straw", "milk tea", "taiwan"]}, "beverage-box": {"a": "beverage box", "b": "1F9C3", "j": ["beverage", "drink", "sweet", "straw", "juice", "box"]}, "mate": {"a": "mate", "b": "1F9C9", "j": ["drink", "beverage", "tea"]}, "ice": {"a": "ice", "b": "1F9CA", "j": ["iceberg", "cold", "ice cube", "water"]}, "chopsticks": {"a": "chopsticks", "b": "1F962", "j": ["food", "jeotgarak", "kuaizi", "hashi"]}, "fork-and-knife-with-plate": {"a": "fork and knife with plate", "b": "1F37D", "j": ["meal", "plate", "dinner", "knife", "eat", "lunch", "cooking", "food", "restaurant", "fork"]}, "fork-and-knife": {"a": "fork and knife", "b": "1F374", "j": ["knife", "cutlery", "cooking", "kitchen", "fork"]}, "spoon": {"a": "spoon", "b": "1F944", "j": ["kitchen", "tableware", "cutlery"]}, "kitchen-knife": {"a": "kitchen knife", "b": "1F52A", "j": ["hocho", "knife", "cutlery", "cooking", "weapon", "blade", "kitchen", "tool"]}, "amphora": {"a": "amphora", "b": "1F3FA", "j": ["Aquarius", "jar", "drink", "cooking", "zodiac", "vase", "jug"]}, "globe-showing-europeafrica": {"a": "globe showing Europe-Africa", "b": "1F30D", "j": ["Africa", "globe", "earth", "international", "Europe", "globe_showing_europe_africa", "world"]}, "globe-showing-americas": {"a": "globe showing Americas", "b": "1F30E", "j": ["globe", "earth", "international", "USA", "Americas", "world"]}, "globe-showing-asiaaustralia": {"a": "globe showing Asia-Australia", "b": "1F30F", "j": ["Asia", "globe", "earth", "globe_showing_asia_australia", "east", "Australia", "international", "world"]}, "globe-with-meridians": {"a": "globe with meridians", "b": "1F310", "j": ["meridians", "globe", "earth", "i18n", "international", "world", "internet", "interweb"]}, "world-map": {"a": "world map", "b": "1F5FA", "j": ["map", "location", "world", "direction"]}, "map-of-japan": {"a": "map of Japan", "b": "1F5FE", "j": ["asia", "map", "japanese", "country", "nation", "Japan"]}, "compass": {"a": "compass", "b": "1F9ED", "j": ["orienteering", "navigation", "magnetic"]}, "snowcapped-mountain": {"a": "snow-capped mountain", "b": "1F3D4", "j": ["snow", "mountain", "environment", "cold", "winter", "nature", "photo", "snow_capped_mountain"]}, "mountain": {"a": "mountain", "b": "26F0", "j": ["environment", "photo", "nature"]}, "volcano": {"a": "volcano", "b": "1F30B", "j": ["mountain", "eruption", "disaster", "nature", "photo"]}, "mount-fuji": {"a": "mount fuji", "b": "1F5FB", "j": ["fuji", "mountain", "japanese", "nature", "photo"]}, "camping": {"a": "camping", "b": "1F3D5", "j": ["outdoors", "photo", "tent"]}, "beach-with-umbrella": {"a": "beach with umbrella", "b": "1F3D6", "j": ["sand", "weather", "umbrella", "mojito", "summer", "sunny", "beach"]}, "desert": {"a": "desert", "b": "1F3DC", "j": ["warm", "saharah", "photo"]}, "desert-island": {"a": "desert island", "b": "1F3DD", "j": ["desert", "photo", "mojito", "tropical", "island"]}, "national-park": {"a": "national park", "b": "1F3DE", "j": ["environment", "park", "photo", "nature"]}, "stadium": {"a": "stadium", "b": "1F3DF", "j": ["venue", "sports", "place", "photo", "concert"]}, "classical-building": {"a": "classical building", "b": "1F3DB", "j": ["classical", "history", "culture", "art"]}, "building-construction": {"a": "building construction", "b": "1F3D7", "j": ["progress", "working", "wip", "construction"]}, "brick": {"a": "brick", "b": "1F9F1", "j": ["bricks", "wall", "clay", "mortar"]}, "rock": {"a": "rock", "b": "1FAA8", "j": ["stone", "heavy", "boulder", "solid"]}, "wood": {"a": "wood", "b": "1FAB5", "j": ["timber", "trunk", "log", "lumber", "nature"]}, "hut": {"a": "hut", "b": "1F6D6", "j": ["house", "structure", "yurt", "roundhouse"]}, "houses": {"a": "houses", "b": "1F3D8", "j": ["photo", "buildings"]}, "derelict-house": {"a": "derelict house", "b": "1F3DA", "j": ["abandon", "house", "broken", "building", "derelict", "evict"]}, "house": {"a": "house", "b": "1F3E0", "j": ["building", "home"]}, "house-with-garden": {"a": "house with garden", "b": "1F3E1", "j": ["garden", "house", "nature", "plant", "home"]}, "office-building": {"a": "office building", "b": "1F3E2", "j": ["bureau", "work", "building"]}, "japanese-post-office": {"a": "Japanese post office", "b": "1F3E3", "j": ["envelope", "communication", "Japanese", "building", "post"]}, "post-office": {"a": "post office", "b": "1F3E4", "j": ["email", "post", "European", "building"]}, "hospital": {"a": "hospital", "b": "1F3E5", "j": ["surgery", "building", "doctor", "medicine", "health"]}, "bank": {"a": "bank", "b": "1F3E6", "j": ["cash", "business", "building", "enterprise", "money", "sales"]}, "hotel": {"a": "hotel", "b": "1F3E8", "j": ["checkin", "accomodation", "building"]}, "love-hotel": {"a": "love hotel", "b": "1F3E9", "j": ["like", "dating", "love", "affection", "hotel"]}, "convenience-store": {"a": "convenience store", "b": "1F3EA", "j": ["shopping", "groceries", "building", "store", "convenience"]}, "school": {"a": "school", "b": "1F3EB", "j": ["learn", "building", "education", "student", "teach"]}, "department-store": {"a": "department store", "b": "1F3EC", "j": ["shopping", "department", "mall", "building", "store"]}, "factory": {"a": "factory", "b": "1F3ED", "j": ["smoke", "pollution", "building", "industry"]}, "japanese-castle": {"a": "Japanese castle", "b": "1F3EF", "j": ["castle", "Japanese", "photo", "building"]}, "castle": {"a": "castle", "b": "1F3F0", "j": ["history", "royalty", "European", "building"]}, "wedding": {"a": "wedding", "b": "1F492", "j": ["love", "like", "marriage", "bride", "groom", "romance", "chapel", "affection", "couple"]}, "tokyo-tower": {"a": "Tokyo tower", "b": "1F5FC", "j": ["japanese", "photo", "Tokyo", "tower"]}, "statue-of-liberty": {"a": "Statue of Liberty", "b": "1F5FD", "j": ["newyork", "liberty", "statue", "american"]}, "church": {"a": "church", "b": "26EA", "j": ["cross", "Christian", "building", "christ", "religion"]}, "mosque": {"a": "mosque", "b": "1F54C", "j": ["minaret", "worship", "islam", "Muslim", "religion"]}, "hindu-temple": {"a": "hindu temple", "b": "1F6D5", "j": ["hindu", "temple", "religion"]}, "synagogue": {"a": "synagogue", "b": "1F54D", "j": ["jewish", "Jew", "worship", "Jewish", "judaism", "temple", "religion"]}, "shinto-shrine": {"a": "shinto shrine", "b": "26E9", "j": ["kyoto", "shinto", "temple", "religion", "shrine", "japan"]}, "kaaba": {"a": "kaaba", "b": "1F54B", "j": ["mecca", "mosque", "islam", "Muslim", "religion"]}, "fountain": {"a": "fountain", "b": "26F2", "j": ["fresh", "photo", "summer", "water"]}, "tent": {"a": "tent", "b": "26FA", "j": ["photo", "outdoors", "camping"]}, "foggy": {"a": "foggy", "b": "1F301", "j": ["fog", "mountain", "photo"]}, "night-with-stars": {"a": "night with stars", "b": "1F303", "j": ["night", "downtown", "evening", "star", "city"]}, "cityscape": {"a": "cityscape", "b": "1F3D9", "j": ["night life", "urban", "city", "photo"]}, "sunrise-over-mountains": {"a": "sunrise over mountains", "b": "1F304", "j": ["sun", "sunrise", "mountain", "vacation", "morning", "view", "photo"]}, "sunrise": {"a": "sunrise", "b": "1F305", "j": ["sun", "vacation", "morning", "photo", "view"]}, "cityscape-at-dusk": {"a": "cityscape at dusk", "b": "1F306", "j": ["evening", "sunset", "city", "landscape", "sky", "dusk", "photo", "buildings"]}, "sunset": {"a": "sunset", "b": "1F307", "j": ["sun", "good morning", "dawn", "dusk", "photo"]}, "bridge-at-night": {"a": "bridge at night", "b": "1F309", "j": ["night", "sanfrancisco", "photo", "bridge"]}, "hot-springs": {"a": "hot springs", "b": "2668", "j": ["bath", "springs", "hot", "steaming", "relax", "warm", "hotsprings"]}, "carousel-horse": {"a": "carousel horse", "b": "1F3A0", "j": ["carnival", "carousel", "photo", "horse"]}, "ferris-wheel": {"a": "ferris wheel", "b": "1F3A1", "j": ["wheel", "carnival", "photo", "londoneye", "ferris", "amusement park"]}, "roller-coaster": {"a": "roller coaster", "b": "1F3A2", "j": ["coaster", "playground", "carnival", "photo", "fun", "roller", "amusement park"]}, "barber-pole": {"a": "barber pole", "b": "1F488", "j": ["salon", "hair", "style", "barber", "pole", "haircut"]}, "circus-tent": {"a": "circus tent", "b": "1F3AA", "j": ["festival", "carnival", "tent", "party", "circus"]}, "locomotive": {"a": "locomotive", "b": "1F682", "j": ["railway", "vehicle", "transportation", "engine", "train", "steam"]}, "railway-car": {"a": "railway car", "b": "1F683", "j": ["railway", "vehicle", "electric", "transportation", "tram", "trolleybus", "train", "car"]}, "highspeed-train": {"a": "high-speed train", "b": "1F684", "j": ["railway", "vehicle", "transportation", "speed", "high_speed_train", "train", "shinkansen"]}, "bullet-train": {"a": "bullet train", "b": "1F685", "j": ["railway", "vehicle", "bullet", "transportation", "fast", "speed", "travel", "shinkansen", "train", "public"]}, "train": {"a": "train", "b": "1F686", "j": ["railway", "vehicle", "transportation"]}, "metro": {"a": "metro", "b": "1F687", "j": ["subway", "transportation", "mrt", "blue-square", "underground", "tube"]}, "light-rail": {"a": "light rail", "b": "1F688", "j": ["railway", "vehicle", "transportation"]}, "station": {"a": "station", "b": "1F689", "j": ["railway", "vehicle", "transportation", "train", "public"]}, "tram": {"a": "tram", "b": "1F68A", "j": ["vehicle", "transportation", "trolleybus"]}, "monorail": {"a": "monorail", "b": "1F69D", "j": ["vehicle", "transportation"]}, "mountain-railway": {"a": "mountain railway", "b": "1F69E", "j": ["railway", "vehicle", "mountain", "transportation", "car"]}, "tram-car": {"a": "tram car", "b": "1F68B", "j": ["vehicle", "car", "transportation", "carriage", "trolleybus", "tram", "travel", "public"]}, "bus": {"a": "bus", "b": "1F68C", "j": ["vehicle", "transportation", "car"]}, "oncoming-bus": {"a": "oncoming bus", "b": "1F68D", "j": ["transportation", "vehicle", "oncoming", "bus"]}, "trolleybus": {"a": "trolleybus", "b": "1F68E", "j": ["vehicle", "bus", "transportation", "tram", "trolley", "bart"]}, "minibus": {"a": "minibus", "b": "1F690", "j": ["bus", "vehicle", "transportation", "car"]}, "ambulance": {"a": "ambulance", "b": "1F691", "j": ["hospital", "vehicle", "911", "health"]}, "fire-engine": {"a": "fire engine", "b": "1F692", "j": ["vehicle", "truck", "transportation", "engine", "cars", "fire"]}, "police-car": {"a": "police car", "b": "1F693", "j": ["vehicle", "police", "law", "transportation", "enforcement", "cars", "legal", "patrol", "car"]}, "oncoming-police-car": {"a": "oncoming police car", "b": "1F694", "j": ["vehicle", "police", "law", "oncoming", "enforcement", "911", "legal", "car"]}, "taxi": {"a": "taxi", "b": "1F695", "j": ["vehicle", "transportation", "cars", "uber"]}, "oncoming-taxi": {"a": "oncoming taxi", "b": "1F696", "j": ["vehicle", "oncoming", "cars", "uber", "taxi"]}, "automobile": {"a": "automobile", "b": "1F697", "j": ["vehicle", "transportation", "car", "red"]}, "oncoming-automobile": {"a": "oncoming automobile", "b": "1F698", "j": ["vehicle", "oncoming", "transportation", "automobile", "car"]}, "sport-utility-vehicle": {"a": "sport utility vehicle", "b": "1F699", "j": ["recreational", "vehicle", "sport utility", "transportation"]}, "pickup-truck": {"a": "pickup truck", "b": "1F6FB", "j": ["truck", "transportation", "pick-up", "pickup", "car"]}, "delivery-truck": {"a": "delivery truck", "b": "1F69A", "j": ["delivery", "transportation", "truck", "cars"]}, "articulated-lorry": {"a": "articulated lorry", "b": "1F69B", "j": ["vehicle", "truck", "lorry", "transportation", "express", "semi", "cars"]}, "tractor": {"a": "tractor", "b": "1F69C", "j": ["vehicle", "agriculture", "farming", "car"]}, "racing-car": {"a": "racing car", "b": "1F3CE", "j": ["formula", "f1", "fast", "sports", "race", "racing", "car"]}, "motorcycle": {"a": "motorcycle", "b": "1F3CD", "j": ["fast", "race", "sports", "racing"]}, "motor-scooter": {"a": "motor scooter", "b": "1F6F5", "j": ["vehicle", "scooter", "vespa", "sasha", "motor"]}, "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", "transportation", "move"]}, "bicycle": {"a": "bicycle", "b": "1F6B2", "j": ["bike", "hipster", "exercise", "sports"]}, "kick-scooter": {"a": "kick scooter", "b": "1F6F4", "j": ["scooter", "razor", "vehicle", "kick"]}, "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", "stop", "transportation", "busstop", "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": ["barrell", "drum", "oil"]}, "fuel-pump": {"a": "fuel pump", "b": "26FD", "j": ["petroleum", "gas", "fuel", "pump", "fuelpump", "station", "gas station", "diesel"]}, "police-car-light": {"a": "police car light", "b": "1F6A8", "j": ["revolving", "beacon", "police", "alert", "law", "pinged", "emergency", "light", "ambulance", "911", "legal", "error", "car"]}, "horizontal-traffic-light": {"a": "horizontal traffic light", "b": "1F6A5", "j": ["transportation", "traffic", "light", "signal"]}, "vertical-traffic-light": {"a": "vertical traffic light", "b": "1F6A6", "j": ["traffic", "transportation", "light", "driving", "signal"]}, "stop-sign": {"a": "stop sign", "b": "1F6D1", "j": ["octagonal", "sign", "stop"]}, "construction": {"a": "construction", "b": "1F6A7", "j": ["warning", "wip", "barrier", "progress", "caution"]}, "anchor": {"a": "anchor", "b": "2693", "j": ["boat", "sea", "ferry", "ship", "tool"]}, "sailboat": {"a": "sailboat", "b": "26F5", "j": ["yacht", "resort", "boat", "sailing", "sea", "transportation", "water", "ship", "summer"]}, "canoe": {"a": "canoe", "b": "1F6F6", "j": ["paddle", "ship", "water", "boat"]}, "speedboat": {"a": "speedboat", "b": "1F6A4", "j": ["vehicle", "boat", "transportation", "ship", "summer"]}, "passenger-ship": {"a": "passenger ship", "b": "1F6F3", "j": ["yacht", "passenger", "cruise", "ferry", "ship"]}, "ferry": {"a": "ferry", "b": "26F4", "j": ["yacht", "ship", "passenger", "boat"]}, "motor-boat": {"a": "motor boat", "b": "1F6E5", "j": ["ship", "motorboat", "boat"]}, "ship": {"a": "ship", "b": "1F6A2", "j": ["titanic", "passenger", "boat", "transportation", "deploy"]}, "airplane": {"a": "airplane", "b": "2708", "j": ["vehicle", "flight", "transportation", "fly", "aeroplane"]}, "small-airplane": {"a": "small airplane", "b": "1F6E9", "j": ["vehicle", "flight", "transportation", "fly", "airplane", "aeroplane"]}, "airplane-departure": {"a": "airplane departure", "b": "1F6EB", "j": ["departure", "flight", "check-in", "airport", "airplane", "departures", "aeroplane", "landing"]}, "airplane-arrival": {"a": "airplane arrival", "b": "1F6EC", "j": ["arrivals", "arriving", "flight", "boarding", "airport", "airplane", "aeroplane", "landing"]}, "parachute": {"a": "parachute", "b": "1FA82", "j": ["skydive", "hang-glide", "fly", "parasail", "glide"]}, "seat": {"a": "seat", "b": "1F4BA", "j": ["bus", "transport", "flight", "chair", "sit", "fly", "airplane"]}, "helicopter": {"a": "helicopter", "b": "1F681", "j": ["vehicle", "transportation", "fly"]}, "suspension-railway": {"a": "suspension railway", "b": "1F69F", "j": ["railway", "vehicle", "transportation", "suspension"]}, "mountain-cableway": {"a": "mountain cableway", "b": "1F6A0", "j": ["vehicle", "mountain", "transportation", "ski", "gondola", "cable"]}, "aerial-tramway": {"a": "aerial tramway", "b": "1F6A1", "j": ["vehicle", "aerial", "transportation", "ski", "gondola", "cable", "tramway", "car"]}, "satellite": {"a": "satellite", "b": "1F6F0", "j": ["communication", "gps", "ISS", "space", "NASA", "orbit", "spaceflight"]}, "rocket": {"a": "rocket", "b": "1F680", "j": ["outer space", "launch", "NASA", "ship", "space", "staffmode", "fly", "outer_space"]}, "flying-saucer": {"a": "flying saucer", "b": "1F6F8", "j": ["transportation", "vehicle", "ufo", "UFO"]}, "bellhop-bell": {"a": "bellhop bell", "b": "1F6CE", "j": ["service", "bellhop", "hotel", "bell"]}, "luggage": {"a": "luggage", "b": "1F9F3", "j": ["packing", "travel"]}, "hourglass-done": {"a": "hourglass done", "b": "231B", "j": ["test", "quiz", "timer", "sand", "limit", "oldschool", "exam", "clock", "time"]}, "hourglass-not-done": {"a": "hourglass not done", "b": "23F3", "j": ["hourglass", "timer", "sand", "oldschool", "countdown", "time"]}, "watch": {"a": "watch", "b": "231A", "j": ["accessories", "clock", "time"]}, "alarm-clock": {"a": "alarm clock", "b": "23F0", "j": ["alarm", "clock", "wake", "time"]}, "stopwatch": {"a": "stopwatch", "b": "23F1", "j": ["clock", "time", "deadline"]}, "timer-clock": {"a": "timer clock", "b": "23F2", "j": ["clock", "alarm", "timer"]}, "mantelpiece-clock": {"a": "mantelpiece clock", "b": "1F570", "j": ["clock", "time"]}, "twelve-oclock": {"a": "twelve o’clock", "b": "1F55B", "j": ["midnight", "12", "12:00", "twelve", "midday", "noon", "late", "o’clock", "early", "twelve_o_clock", "schedule", "00", "clock", "time"]}, "twelvethirty": {"a": "twelve-thirty", "b": "1F567", "j": ["12", "twelve_thirty", "thirty", "twelve", "late", "early", "schedule", "12:30", "clock", "time"]}, "one-oclock": {"a": "one o’clock", "b": "1F550", "j": ["1:00", "1", "late", "time", "early", "o’clock", "schedule", "00", "one_o_clock", "clock", "one"]}, "onethirty": {"a": "one-thirty", "b": "1F55C", "j": ["1", "1:30", "thirty", "late", "time", "early", "schedule", "one_thirty", "clock", "one"]}, "two-oclock": {"a": "two o’clock", "b": "1F551", "j": ["two_o_clock", "2", "2:00", "late", "early", "schedule", "o’clock", "00", "clock", "two", "time"]}, "twothirty": {"a": "two-thirty", "b": "1F55D", "j": ["two_thirty", "2", "thirty", "late", "2:30", "early", "schedule", "clock", "two", "time"]}, "three-oclock": {"a": "three o’clock", "b": "1F552", "j": ["3:00", "three_o_clock", "late", "3", "early", "o’clock", "three", "schedule", "00", "clock", "time"]}, "threethirty": {"a": "three-thirty", "b": "1F55E", "j": ["thirty", "late", "3", "3:30", "three", "three_thirty", "early", "schedule", "clock", "time"]}, "four-oclock": {"a": "four o’clock", "b": "1F553", "j": ["four", "late", "early", "schedule", "o’clock", "4", "00", "four_o_clock", "clock", "4:00", "time"]}, "fourthirty": {"a": "four-thirty", "b": "1F55F", "j": ["thirty", "four", "late", "4:30", "early", "schedule", "four_thirty", "4", "clock", "time"]}, "five-oclock": {"a": "five o’clock", "b": "1F554", "j": ["five_o_clock", "late", "early", "schedule", "o’clock", "five", "00", "5:00", "5", "clock", "time"]}, "fivethirty": {"a": "five-thirty", "b": "1F560", "j": ["five_thirty", "thirty", "late", "early", "schedule", "five", "5", "clock", "5:30", "time"]}, "six-oclock": {"a": "six o’clock", "b": "1F555", "j": ["late", "early", "schedule", "o’clock", "six_o_clock", "6", "dawn", "00", "dusk", "6:00", "clock", "six", "time"]}, "sixthirty": {"a": "six-thirty", "b": "1F561", "j": ["thirty", "6:30", "late", "early", "schedule", "6", "six_thirty", "clock", "six", "time"]}, "seven-oclock": {"a": "seven o’clock", "b": "1F556", "j": ["7:00", "7", "seven_o_clock", "late", "early", "schedule", "o’clock", "seven", "00", "clock", "time"]}, "seventhirty": {"a": "seven-thirty", "b": "1F562", "j": ["seven_thirty", "7", "thirty", "7:30", "late", "early", "schedule", "seven", "clock", "time"]}, "eight-oclock": {"a": "eight o’clock", "b": "1F557", "j": ["8:00", "eight", "late", "o’clock", "early", "schedule", "00", "eight_o_clock", "clock", "8", "time"]}, "eightthirty": {"a": "eight-thirty", "b": "1F563", "j": ["8:30", "thirty", "eight_thirty", "eight", "late", "early", "schedule", "clock", "8", "time"]}, "nine-oclock": {"a": "nine o’clock", "b": "1F558", "j": ["nine", "9:00", "9", "nine_o_clock", "late", "early", "schedule", "o’clock", "00", "clock", "time"]}, "ninethirty": {"a": "nine-thirty", "b": "1F564", "j": ["nine", "9", "thirty", "late", "nine_thirty", "early", "schedule", "9:30", "clock", "time"]}, "ten-oclock": {"a": "ten o’clock", "b": "1F559", "j": ["ten", "late", "early", "schedule", "o’clock", "10:00", "00", "10", "ten_o_clock", "clock", "time"]}, "tenthirty": {"a": "ten-thirty", "b": "1F565", "j": ["10:30", "thirty", "ten", "late", "early", "schedule", "ten_thirty", "10", "clock", "time"]}, "eleven-oclock": {"a": "eleven o’clock", "b": "1F55A", "j": ["eleven_o_clock", "eleven", "late", "early", "schedule", "o’clock", "00", "11", "clock", "11:00", "time"]}, "eleventhirty": {"a": "eleven-thirty", "b": "1F566", "j": ["thirty", "eleven", "eleven_thirty", "late", "early", "schedule", "11:30", "11", "clock", "time"]}, "new-moon": {"a": "new moon", "b": "1F311", "j": ["night", "sleep", "evening", "moon", "nature", "space", "planet", "dark", "twilight"]}, "waxing-crescent-moon": {"a": "waxing crescent moon", "b": "1F312", "j": ["night", "sleep", "evening", "crescent", "moon", "nature", "space", "planet", "waxing", "twilight"]}, "first-quarter-moon": {"a": "first quarter moon", "b": "1F313", "j": ["night", "sleep", "evening", "moon", "nature", "space", "planet", "quarter", "twilight"]}, "waxing-gibbous-moon": {"a": "waxing gibbous moon", "b": "1F314", "j": ["night", "sleep", "evening", "moon", "nature", "sky", "space", "planet", "gibbous", "gray", "waxing", "twilight"]}, "full-moon": {"a": "full moon", "b": "1F315", "j": ["night", "full", "sleep", "evening", "moon", "nature", "space", "yellow", "planet", "twilight"]}, "waning-gibbous-moon": {"a": "waning gibbous moon", "b": "1F316", "j": ["night", "sleep", "waning", "evening", "moon", "nature", "waxing_gibbous_moon", "space", "planet", "gibbous", "twilight"]}, "last-quarter-moon": {"a": "last quarter moon", "b": "1F317", "j": ["night", "sleep", "evening", "moon", "nature", "space", "planet", "quarter", "twilight"]}, "waning-crescent-moon": {"a": "waning crescent moon", "b": "1F318", "j": ["night", "sleep", "waning", "evening", "crescent", "moon", "nature", "space", "planet", "twilight"]}, "crescent-moon": {"a": "crescent moon", "b": "1F319", "j": ["night", "sleep", "evening", "crescent", "moon", "sky", "magic"]}, "new-moon-face": {"a": "new moon face", "b": "1F31A", "j": ["night", "sleep", "evening", "moon", "face", "nature", "space", "planet", "twilight"]}, "first-quarter-moon-face": {"a": "first quarter moon face", "b": "1F31B", "j": ["night", "sleep", "evening", "moon", "face", "nature", "space", "planet", "quarter", "twilight"]}, "last-quarter-moon-face": {"a": "last quarter moon face", "b": "1F31C", "j": ["night", "sleep", "evening", "moon", "face", "nature", "space", "planet", "quarter", "twilight"]}, "thermometer": {"a": "thermometer", "b": "1F321", "j": ["weather", "hot", "cold", "temperature"]}, "sun": {"a": "sun", "b": "2600", "j": ["brightness", "rays", "spring", "bright", "nature", "weather", "summer", "sunny", "beach"]}, "full-moon-face": {"a": "full moon face", "b": "1F31D", "j": ["night", "full", "sleep", "evening", "moon", "bright", "face", "nature", "space", "planet", "twilight"]}, "sun-with-face": {"a": "sun with face", "b": "1F31E", "j": ["sun", "bright", "face", "nature", "sky", "morning"]}, "ringed-planet": {"a": "ringed planet", "b": "1FA90", "j": ["outerspace", "saturnine", "saturn"]}, "star": {"a": "star", "b": "2B50", "j": ["night", "yellow"]}, "glowing-star": {"a": "glowing star", "b": "1F31F", "j": ["awesome", "night", "shining", "star", "good", "glittery", "glow", "sparkle", "magic"]}, "shooting-star": {"a": "shooting star", "b": "1F320", "j": ["shooting", "night", "star", "falling", "photo"]}, "milky-way": {"a": "milky way", "b": "1F30C", "j": ["stars", "space", "photo"]}, "cloud": {"a": "cloud", "b": "2601", "j": ["weather", "sky"]}, "sun-behind-cloud": {"a": "sun behind cloud", "b": "26C5", "j": ["sun", "spring", "cloud", "nature", "weather", "morning", "fall", "cloudy"]}, "cloud-with-lightning-and-rain": {"a": "cloud with lightning and rain", "b": "26C8", "j": ["lightning", "rain", "cloud", "weather", "thunder"]}, "sun-behind-small-cloud": {"a": "sun behind small cloud", "b": "1F324", "j": ["sun", "weather", "cloud"]}, "sun-behind-large-cloud": {"a": "sun behind large cloud", "b": "1F325", "j": ["sun", "weather", "cloud"]}, "sun-behind-rain-cloud": {"a": "sun behind rain cloud", "b": "1F326", "j": ["sun", "weather", "rain", "cloud"]}, "cloud-with-rain": {"a": "cloud with rain", "b": "1F327", "j": ["weather", "rain", "cloud"]}, "cloud-with-snow": {"a": "cloud with snow", "b": "1F328", "j": ["weather", "snow", "cold", "cloud"]}, "cloud-with-lightning": {"a": "cloud with lightning", "b": "1F329", "j": ["weather", "lightning", "thunder", "cloud"]}, "tornado": {"a": "tornado", "b": "1F32A", "j": ["cyclone", "cloud", "weather", "twister", "whirlwind"]}, "fog": {"a": "fog", "b": "1F32B", "j": ["weather", "cloud"]}, "wind-face": {"a": "wind face", "b": "1F32C", "j": ["air", "blow", "face", "cloud", "gust", "wind"]}, "cyclone": {"a": "cyclone", "b": "1F300", "j": ["blue", "spin", "whirlpool", "typhoon", "cloud", "weather", "twister", "spiral", "dizzy", "hurricane", "vortex", "swirl", "tornado"]}, "rainbow": {"a": "rainbow", "b": "1F308", "j": ["rain", "spring", "nature", "sky", "photo", "unicorn_face", "happy"]}, "closed-umbrella": {"a": "closed umbrella", "b": "1F302", "j": ["rain", "umbrella", "weather", "clothing", "drizzle"]}, "umbrella": {"a": "umbrella", "b": "2602", "j": ["weather", "clothing", "rain", "spring"]}, "umbrella-with-rain-drops": {"a": "umbrella with rain drops", "b": "2614", "j": ["drop", "rain", "spring", "rainy", "umbrella", "clothing", "weather"]}, "umbrella-on-ground": {"a": "umbrella on ground", "b": "26F1", "j": ["sun", "rain", "umbrella", "weather", "summer"]}, "high-voltage": {"a": "high voltage", "b": "26A1", "j": ["danger", "lightning", "electric", "lightning bolt", "zap", "fast", "weather", "thunder", "voltage"]}, "snowflake": {"a": "snowflake", "b": "2744", "j": ["snow", "cold", "xmas", "winter", "season", "weather", "christmas"]}, "snowman": {"a": "snowman", "b": "2603", "j": ["snow", "cold", "winter", "xmas", "season", "weather", "frozen", "christmas"]}, "snowman-without-snow": {"a": "snowman without snow", "b": "26C4", "j": ["snow", "snowman", "cold", "winter", "xmas", "season", "weather", "frozen", "without_snow", "christmas"]}, "comet": {"a": "comet", "b": "2604", "j": ["space"]}, "fire": {"a": "fire", "b": "1F525", "j": ["flame", "cook", "tool", "hot"]}, "droplet": {"a": "droplet", "b": "1F4A7", "j": ["drop", "sweat", "drip", "spring", "cold", "water", "comic", "faucet"]}, "water-wave": {"a": "water wave", "b": "1F30A", "j": ["wave", "tsunami", "disaster", "sea", "water", "nature", "ocean"]}, "jackolantern": {"a": "jack-o-lantern", "b": "1F383", "j": ["jack_o_lantern", "creepy", "celebration", "halloween", "pumpkin", "light", "fall", "jack", "lantern"]}, "christmas-tree": {"a": "Christmas tree", "b": "1F384", "j": ["vacation", "festival", "xmas", "celebration", "Christmas", "december", "tree"]}, "fireworks": {"a": "fireworks", "b": "1F386", "j": ["congratulations", "festival", "celebration", "carnival", "photo"]}, "sparkler": {"a": "sparkler", "b": "1F387", "j": ["night", "fireworks", "shine", "stars", "celebration", "sparkle"]}, "firecracker": {"a": "firecracker", "b": "1F9E8", "j": ["boom", "fireworks", "explode", "explosion", "explosive", "dynamite"]}, "sparkles": {"a": "sparkles", "b": "2728", "j": ["awesome", "shiny", "cool", "shine", "star", "stars", "*", "good", "sparkle", "magic"]}, "balloon": {"a": "balloon", "b": "1F388", "j": ["birthday", "circus", "celebration", "party"]}, "party-popper": {"a": "party popper", "b": "1F389", "j": ["birthday", "tada", "congratulations", "popper", "celebration", "party", "circus", "magic"]}, "confetti-ball": {"a": "confetti ball", "b": "1F38A", "j": ["birthday", "festival", "ball", "celebration", "party", "confetti", "circus"]}, "tanabata-tree": {"a": "tanabata tree", "b": "1F38B", "j": ["banner", "Japanese", "nature", "celebration", "plant", "summer", "tree", "branch"]}, "pine-decoration": {"a": "pine decoration", "b": "1F38D", "j": ["bamboo", "panda", "pine", "Japanese", "nature", "celebration", "plant", "vegetable"]}, "japanese-dolls": {"a": "Japanese dolls", "b": "1F38E", "j": ["toy", "Japanese", "festival", "japanese", "celebration", "kimono", "doll"]}, "carp-streamer": {"a": "carp streamer", "b": "1F38F", "j": ["carp", "banner", "japanese", "fish", "celebration", "streamer", "koinobori"]}, "wind-chime": {"a": "wind chime", "b": "1F390", "j": ["ding", "spring", "chime", "bell", "celebration", "nature", "wind"]}, "moon-viewing-ceremony": {"a": "moon viewing ceremony", "b": "1F391", "j": ["tsukimi", "asia", "moon", "celebration", "ceremony", "photo", "japan"]}, "red-envelope": {"a": "red envelope", "b": "1F9E7", "j": ["hóngbāo", "lai see", "gift", "money", "good luck"]}, "ribbon": {"a": "ribbon", "b": "1F380", "j": ["bowtie", "pink", "girl", "decoration", "celebration"]}, "wrapped-gift": {"a": "wrapped gift", "b": "1F381", "j": ["birthday", "xmas", "celebration", "gift", "present", "wrapped", "box", "christmas"]}, "reminder-ribbon": {"a": "reminder ribbon", "b": "1F397", "j": ["cause", "support", "sports", "celebration", "reminder", "awareness", "ribbon"]}, "admission-tickets": {"a": "admission tickets", "b": "1F39F", "j": ["admission", "sports", "ticket", "entrance", "concert"]}, "ticket": {"a": "ticket", "b": "1F3AB", "j": ["admission", "event", "pass", "concert"]}, "military-medal": {"a": "military medal", "b": "1F396", "j": ["medal", "military", "award", "celebration", "army", "winning"]}, "trophy": {"a": "trophy", "b": "1F3C6", "j": ["ftw", "award", "contest", "place", "ceremony", "win", "prize"]}, "sports-medal": {"a": "sports medal", "b": "1F3C5", "j": ["medal", "winning", "award"]}, "1st-place-medal": {"a": "1st place medal", "b": "1F947", "j": ["medal", "award", "first", "gold", "winning"]}, "2nd-place-medal": {"a": "2nd place medal", "b": "1F948", "j": ["second", "medal", "award", "silver"]}, "3rd-place-medal": {"a": "3rd place medal", "b": "1F949", "j": ["medal", "award", "third", "bronze"]}, "soccer-ball": {"a": "soccer ball", "b": "26BD", "j": ["sports", "soccer", "football", "ball"]}, "baseball": {"a": "baseball", "b": "26BE", "j": ["balls", "sports", "ball"]}, "softball": {"a": "softball", "b": "1F94E", "j": ["underarm", "sports", "ball", "balls", "glove"]}, "basketball": {"a": "basketball", "b": "1F3C0", "j": ["hoop", "sports", "ball", "balls", "NBA"]}, "volleyball": {"a": "volleyball", "b": "1F3D0", "j": ["game", "balls", "sports", "ball"]}, "american-football": {"a": "american football", "b": "1F3C8", "j": ["sports", "NFL", "ball", "balls", "american", "football"]}, "rugby-football": {"a": "rugby football", "b": "1F3C9", "j": ["team", "sports", "ball", "rugby", "football"]}, "tennis": {"a": "tennis", "b": "1F3BE", "j": ["racquet", "sports", "ball", "balls", "green"]}, "flying-disc": {"a": "flying disc", "b": "1F94F", "j": ["frisbee", "ultimate", "sports"]}, "bowling": {"a": "bowling", "b": "1F3B3", "j": ["play", "sports", "ball", "game", "fun"]}, "cricket-game": {"a": "cricket game", "b": "1F3CF", "j": ["ball", "game", "bat", "sports"]}, "field-hockey": {"a": "field hockey", "b": "1F3D1", "j": ["stick", "ball", "sports", "field", "game", "hockey"]}, "ice-hockey": {"a": "ice hockey", "b": "1F3D2", "j": ["stick", "hockey", "ice", "sports", "game", "puck"]}, "lacrosse": {"a": "lacrosse", "b": "1F94D", "j": ["stick", "goal", "sports", "ball"]}, "ping-pong": {"a": "ping pong", "b": "1F3D3", "j": ["pingpong", "bat", "sports", "paddle", "ball", "game", "table tennis"]}, "badminton": {"a": "badminton", "b": "1F3F8", "j": ["shuttlecock", "racquet", "sports", "game", "birdie"]}, "boxing-glove": {"a": "boxing glove", "b": "1F94A", "j": ["boxing", "fighting", "sports", "glove"]}, "martial-arts-uniform": {"a": "martial arts uniform", "b": "1F94B", "j": ["judo", "martial arts", "taekwondo", "uniform", "karate"]}, "goal-net": {"a": "goal net", "b": "1F945", "j": ["goal", "net", "sports"]}, "flag-in-hole": {"a": "flag in hole", "b": "26F3", "j": ["business", "flag", "golf", "sports", "summer", "hole"]}, "ice-skate": {"a": "ice skate", "b": "26F8", "j": ["ice", "skate", "sports"]}, "fishing-pole": {"a": "fishing pole", "b": "1F3A3", "j": ["hobby", "fish", "food", "pole", "summer"]}, "diving-mask": {"a": "diving mask", "b": "1F93F", "j": ["scuba", "diving", "sport", "ocean", "snorkeling"]}, "running-shirt": {"a": "running shirt", "b": "1F3BD", "j": ["sash", "shirt", "athletics", "play", "pageant", "running"]}, "skis": {"a": "skis", "b": "1F3BF", "j": ["snow", "cold", "winter", "sports", "ski"]}, "sled": {"a": "sled", "b": "1F6F7", "j": ["luge", "toboggan", "sledge", "sleigh"]}, "curling-stone": {"a": "curling stone", "b": "1F94C", "j": ["game", "rock", "sports"]}, "bullseye": {"a": "bullseye", "b": "1F3AF", "j": ["target", "direct hit", "play", "dart", "hit", "bar", "game", "direct_hit"]}, "yoyo": {"a": "yo-yo", "b": "1FA80", "j": ["fluctuate", "toy", "yo_yo"]}, "kite": {"a": "kite", "b": "1FA81", "j": ["soar", "wind", "fly"]}, "pool-8-ball": {"a": "pool 8 ball", "b": "1F3B1", "j": ["pool", "hobby", "billiard", "eight", "ball", "game", "luck", "8", "magic"]}, "crystal-ball": {"a": "crystal ball", "b": "1F52E", "j": ["disco", "tool", "crystal", "ball", "fairy tale", "fantasy", "party", "fortune_teller", "circus", "fortune", "magic"]}, "magic-wand": {"a": "magic wand", "b": "1FA84", "j": ["witch", "power", "supernature", "wizard", "magic"]}, "nazar-amulet": {"a": "nazar amulet", "b": "1F9FF", "j": ["talisman", "charm", "bead", "nazar", "evil-eye"]}, "video-game": {"a": "video game", "b": "1F3AE", "j": ["controller", "play", "game", "PS4", "console"]}, "joystick": {"a": "joystick", "b": "1F579", "j": ["play", "game", "video game"]}, "slot-machine": {"a": "slot machine", "b": "1F3B0", "j": ["slot", "gamble", "casino", "game", "luck", "fruit machine", "bet", "vegas"]}, "game-die": {"a": "game die", "b": "1F3B2", "j": ["random", "play", "dice", "game", "luck", "die", "tabletop"]}, "puzzle-piece": {"a": "puzzle piece", "b": "1F9E9", "j": ["puzzle", "interlocking", "clue", "piece", "jigsaw"]}, "teddy-bear": {"a": "teddy bear", "b": "1F9F8", "j": ["plush", "toy", "plaything", "stuffed"]}, "piata": {"a": "piñata", "b": "1FA85", "j": ["mexico", "celebration", "candy", "pinata", "party"]}, "nesting-dolls": {"a": "nesting dolls", "b": "1FA86", "j": ["nesting", "toy", "russia", "matryoshka", "doll"]}, "spade-suit": {"a": "spade suit", "b": "2660", "j": ["card", "cards", "suits", "game", "poker", "magic"]}, "heart-suit": {"a": "heart suit", "b": "2665", "j": ["card", "cards", "suits", "game", "poker", "magic"]}, "diamond-suit": {"a": "diamond suit", "b": "2666", "j": ["card", "cards", "suits", "game", "poker", "magic"]}, "club-suit": {"a": "club suit", "b": "2663", "j": ["card", "cards", "suits", "game", "poker", "magic"]}, "chess-pawn": {"a": "chess pawn", "b": "265F", "j": ["chess", "expendable", "dupe"]}, "joker": {"a": "joker", "b": "1F0CF", "j": ["play", "magic", "card", "game", "poker", "wildcard", "cards"]}, "mahjong-red-dragon": {"a": "mahjong red dragon", "b": "1F004", "j": ["play", "mahjong", "kanji", "game", "red", "chinese"]}, "flower-playing-cards": {"a": "flower playing cards", "b": "1F3B4", "j": ["flower", "sunset", "card", "Japanese", "playing", "game", "red"]}, "performing-arts": {"a": "performing arts", "b": "1F3AD", "j": ["theater", "acting", "mask", "performing", "drama", "art", "theatre"]}, "framed-picture": {"a": "framed picture", "b": "1F5BC", "j": ["picture", "photography", "painting", "frame", "museum", "art"]}, "artist-palette": {"a": "artist palette", "b": "1F3A8", "j": ["draw", "design", "colors", "paint", "palette", "painting", "museum", "art"]}, "thread": {"a": "thread", "b": "1F9F5", "j": ["string", "needle", "spool", "sewing"]}, "sewing-needle": {"a": "sewing needle", "b": "1FAA1", "j": ["sutures", "stitches", "embroidery", "tailoring", "sewing", "needle"]}, "yarn": {"a": "yarn", "b": "1F9F6", "j": ["crochet", "knit", "ball"]}, "knot": {"a": "knot", "b": "1FAA2", "j": ["tangled", "twist", "rope", "tie", "twine", "scout"]}, "glasses": {"a": "glasses", "b": "1F453", "j": ["accessories", "nerdy", "geek", "eyeglasses", "eyesight", "clothing", "eye", "fashion", "eyewear", "dork"]}, "sunglasses": {"a": "sunglasses", "b": "1F576", "j": ["accessories", "glasses", "cool", "face", "eye", "eyewear", "dark"]}, "goggles": {"a": "goggles", "b": "1F97D", "j": ["welding", "swimming", "protection", "safety", "eye protection", "eyes"]}, "lab-coat": {"a": "lab coat", "b": "1F97C", "j": ["scientist", "chemist", "doctor", "experiment"]}, "safety-vest": {"a": "safety vest", "b": "1F9BA", "j": ["protection", "safety", "emergency", "vest"]}, "necktie": {"a": "necktie", "b": "1F454", "j": ["shirt", "business", "cloth", "formal", "tie", "clothing", "suitup", "fashion"]}, "tshirt": {"a": "t-shirt", "b": "1F455", "j": ["t_shirt", "shirt", "cloth", "clothing", "fashion", "tee", "casual"]}, "jeans": {"a": "jeans", "b": "1F456", "j": ["shopping", "trousers", "clothing", "fashion", "pants"]}, "scarf": {"a": "scarf", "b": "1F9E3", "j": ["neck", "winter", "clothes"]}, "gloves": {"a": "gloves", "b": "1F9E4", "j": ["hands", "hand", "winter", "clothes"]}, "coat": {"a": "coat", "b": "1F9E5", "j": ["jacket"]}, "socks": {"a": "socks", "b": "1F9E6", "j": ["stockings", "stocking", "clothes"]}, "dress": {"a": "dress", "b": "1F457", "j": ["shopping", "clothing", "fashion", "clothes"]}, "kimono": {"a": "kimono", "b": "1F458", "j": ["dress", "japanese", "clothing", "fashion", "women", "female"]}, "sari": {"a": "sari", "b": "1F97B", "j": ["dress", "clothing"]}, "onepiece-swimsuit": {"a": "one-piece swimsuit", "b": "1FA71", "j": ["one_piece_swimsuit", "fashion", "bathing suit"]}, "briefs": {"a": "briefs", "b": "1FA72", "j": ["swimsuit", "one-piece", "clothing", "underwear", "bathing suit"]}, "shorts": {"a": "shorts", "b": "1FA73", "j": ["clothing", "underwear", "pants", "bathing suit"]}, "bikini": {"a": "bikini", "b": "1F459", "j": ["swim", "woman", "girl", "swimming", "clothing", "fashion", "summer", "beach", "female"]}, "womans-clothes": {"a": "woman’s clothes", "b": "1F45A", "j": ["woman_s_clothes", "woman", "shopping_bags", "clothing", "fashion", "female"]}, "purse": {"a": "purse", "b": "1F45B", "j": ["accessories", "shopping", "clothing", "fashion", "money", "coin", "sales"]}, "handbag": {"a": "handbag", "b": "1F45C", "j": ["accessories", "shopping", "accessory", "clothing", "bag", "fashion", "purse"]}, "clutch-bag": {"a": "clutch bag", "b": "1F45D", "j": ["accessories", "shopping", "pouch", "clothing", "bag"]}, "shopping-bags": {"a": "shopping bags", "b": "1F6CD", "j": ["shopping", "mall", "buy", "bag", "purchase", "hotel"]}, "backpack": {"a": "backpack", "b": "1F392", "j": ["satchel", "school", "education", "student", "bag", "rucksack"]}, "thong-sandal": {"a": "thong sandal", "b": "1FA74", "j": ["beach sandals", "thong sandals", "zōri", "thongs", "sandals", "summer", "footwear"]}, "mans-shoe": {"a": "man’s shoe", "b": "1F45E", "j": ["man", "male", "man_s_shoe", "clothing", "fashion", "shoe"]}, "running-shoe": {"a": "running shoe", "b": "1F45F", "j": ["sneakers", "sneaker", "athletic", "shoes", "sports", "clothing", "shoe"]}, "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": ["woman", "pumps", "shoes", "heel", "high_heeled_shoe", "clothing", "fashion", "stiletto", "shoe", "female"]}, "womans-sandal": {"a": "woman’s sandal", "b": "1F461", "j": ["sandal", "woman", "shoes", "flip flops", "clothing", "woman_s_sandal", "fashion", "shoe"]}, "ballet-shoes": {"a": "ballet shoes", "b": "1FA70", "j": ["ballet", "dance"]}, "womans-boot": {"a": "woman’s boot", "b": "1F462", "j": ["woman", "shoes", "clothing", "fashion", "shoe", "boot", "woman_s_boot"]}, "crown": {"a": "crown", "b": "1F451", "j": ["royalty", "kod", "leader", "king", "clothing", "lord", "queen"]}, "womans-hat": {"a": "woman’s hat", "b": "1F452", "j": ["accessories", "spring", "woman", "lady", "woman_s_hat", "hat", "clothing", "fashion", "female"]}, "top-hat": {"a": "top hat", "b": "1F3A9", "j": ["classy", "top", "magic", "hat", "clothing", "tophat", "circus", "gentleman"]}, "graduation-cap": {"a": "graduation cap", "b": "1F393", "j": ["learn", "school", "hat", "college", "cap", "celebration", "clothing", "degree", "education", "legal", "university", "graduation"]}, "billed-cap": {"a": "billed cap", "b": "1F9E2", "j": ["cap", "baseball cap", "baseball"]}, "military-helmet": {"a": "military helmet", "b": "1FA96", "j": ["military", "warrior", "army", "helmet", "protection", "soldier"]}, "rescue-workers-helmet": {"a": "rescue worker’s helmet", "b": "26D1", "j": ["cross", "aid", "build", "construction", "face", "hat", "helmet", "rescue_worker_s_helmet"]}, "prayer-beads": {"a": "prayer beads", "b": "1F4FF", "j": ["dhikr", "religious", "beads", "clothing", "prayer", "necklace", "religion"]}, "lipstick": {"a": "lipstick", "b": "1F484", "j": ["woman", "girl", "makeup", "fashion", "cosmetics", "female"]}, "ring": {"a": "ring", "b": "1F48D", "j": ["wedding", "diamond", "marriage", "valentines", "propose", "fashion", "gem", "engagement", "jewelry"]}, "gem-stone": {"a": "gem stone", "b": "1F48E", "j": ["blue", "diamond", "gem", "ruby", "jewelry", "jewel"]}, "muted-speaker": {"a": "muted speaker", "b": "1F507", "j": ["silence", "speaker", "sound", "mute", "quiet", "silent", "volume"]}, "speaker-low-volume": {"a": "speaker low volume", "b": "1F508", "j": ["silence", "sound", "soft", "broadcast", "volume"]}, "speaker-medium-volume": {"a": "speaker medium volume", "b": "1F509", "j": ["speaker", "broadcast", "medium", "volume"]}, "speaker-high-volume": {"a": "speaker high volume", "b": "1F50A", "j": ["noisy", "noise", "speaker", "broadcast", "volume", "loud"]}, "loudspeaker": {"a": "loudspeaker", "b": "1F4E2", "j": ["public address", "volume", "sound", "loud"]}, "megaphone": {"a": "megaphone", "b": "1F4E3", "j": ["volume", "sound", "cheering", "speaker"]}, "postal-horn": {"a": "postal horn", "b": "1F4EF", "j": ["instrument", "horn", "post", "postal", "music"]}, "bell": {"a": "bell", "b": "1F514", "j": ["sound", "xmas", "chime", "notification", "christmas"]}, "bell-with-slash": {"a": "bell with slash", "b": "1F515", "j": ["forbidden", "sound", "mute", "bell", "quiet", "silent", "volume"]}, "musical-score": {"a": "musical score", "b": "1F3BC", "j": ["clef", "score", "treble", "compose", "music"]}, "musical-note": {"a": "musical note", "b": "1F3B5", "j": ["tone", "score", "sound", "note", "music"]}, "musical-notes": {"a": "musical notes", "b": "1F3B6", "j": ["notes", "note", "score", "music"]}, "studio-microphone": {"a": "studio microphone", "b": "1F399", "j": ["studio", "sing", "artist", "recording", "talkshow", "microphone", "mic", "music"]}, "level-slider": {"a": "level slider", "b": "1F39A", "j": ["slider", "music", "scale", "level"]}, "control-knobs": {"a": "control knobs", "b": "1F39B", "j": ["control", "dial", "music", "knobs"]}, "microphone": {"a": "microphone", "b": "1F3A4", "j": ["PA", "sing", "sound", "talkshow", "mic", "karaoke", "music"]}, "headphone": {"a": "headphone", "b": "1F3A7", "j": ["gadgets", "score", "earbud", "music"]}, "radio": {"a": "radio", "b": "1F4FB", "j": ["communication", "video", "program", "podcast", "music"]}, "saxophone": {"a": "saxophone", "b": "1F3B7", "j": ["sax", "instrument", "jazz", "blues", "music"]}, "accordion": {"a": "accordion", "b": "1FA97", "j": ["squeeze box", "music", "concertina"]}, "guitar": {"a": "guitar", "b": "1F3B8", "j": ["music", "instrument"]}, "musical-keyboard": {"a": "musical keyboard", "b": "1F3B9", "j": ["keyboard", "piano", "compose", "instrument", "music"]}, "trumpet": {"a": "trumpet", "b": "1F3BA", "j": ["brass", "music", "instrument"]}, "violin": {"a": "violin", "b": "1F3BB", "j": ["symphony", "orchestra", "music", "instrument"]}, "banjo": {"a": "banjo", "b": "1FA95", "j": ["instructment", "stringed", "music"]}, "drum": {"a": "drum", "b": "1F941", "j": ["drumsticks", "snare", "music", "instrument"]}, "long-drum": {"a": "long drum", "b": "1FA98", "j": ["drum", "beat", "conga", "music", "rhythm"]}, "mobile-phone": {"a": "mobile phone", "b": "1F4F1", "j": ["apple", "phone", "telephone", "mobile", "gadgets", "dial", "cell", "technology"]}, "mobile-phone-with-arrow": {"a": "mobile phone with arrow", "b": "1F4F2", "j": ["arrow", "incoming", "iphone", "phone", "mobile", "receive", "cell"]}, "telephone": {"a": "telephone", "b": "260E", "j": ["dial", "technology", "communication", "phone"]}, "telephone-receiver": {"a": "telephone receiver", "b": "1F4DE", "j": ["communication", "phone", "telephone", "dial", "receiver", "technology"]}, "pager": {"a": "pager", "b": "1F4DF", "j": ["90s", "bbcall", "oldschool"]}, "fax-machine": {"a": "fax machine", "b": "1F4E0", "j": ["fax", "technology", "communication"]}, "battery": {"a": "battery", "b": "1F50B", "j": ["energy", "power", "sustain"]}, "electric-plug": {"a": "electric plug", "b": "1F50C", "j": ["electric", "power", "charger", "electricity", "plug"]}, "laptop": {"a": "laptop", "b": "1F4BB", "j": ["computer", "display", "monitor", "pc", "screen", "technology", "personal"]}, "desktop-computer": {"a": "desktop computer", "b": "1F5A5", "j": ["computer", "desktop", "computing", "screen", "technology"]}, "printer": {"a": "printer", "b": "1F5A8", "j": ["ink", "computer", "paper"]}, "keyboard": {"a": "keyboard", "b": "2328", "j": ["computer", "text", "input", "type", "technology"]}, "computer-mouse": {"a": "computer mouse", "b": "1F5B1", "j": ["computer", "click"]}, "trackball": {"a": "trackball", "b": "1F5B2", "j": ["trackpad", "computer", "technology"]}, "computer-disk": {"a": "computer disk", "b": "1F4BD", "j": ["minidisk", "computer", "disk", "optical", "record", "90s", "technology", "data"]}, "floppy-disk": {"a": "floppy disk", "b": "1F4BE", "j": ["floppy", "computer", "disk", "80s", "save", "oldschool", "90s", "technology"]}, "optical-disk": {"a": "optical disk", "b": "1F4BF", "j": ["cd", "computer", "disk", "dvd", "optical", "90s", "technology", "disc"]}, "dvd": {"a": "dvd", "b": "1F4C0", "j": ["cd", "computer", "disk", "optical", "blu-ray", "disc"]}, "abacus": {"a": "abacus", "b": "1F9EE", "j": ["calculation"]}, "movie-camera": {"a": "movie camera", "b": "1F3A5", "j": ["cinema", "camera", "record", "film", "movie"]}, "film-frames": {"a": "film frames", "b": "1F39E", "j": ["frames", "movie", "cinema", "film"]}, "film-projector": {"a": "film projector", "b": "1F4FD", "j": ["cinema", "video", "record", "projector", "film", "tape", "movie"]}, "clapper-board": {"a": "clapper board", "b": "1F3AC", "j": ["movie", "clapper", "record", "film"]}, "television": {"a": "television", "b": "1F4FA", "j": ["video", "program", "oldschool", "show", "technology", "tv"]}, "camera": {"a": "camera", "b": "1F4F7", "j": ["gadgets", "photography", "video"]}, "camera-with-flash": {"a": "camera with flash", "b": "1F4F8", "j": ["video", "camera", "gadgets", "photography", "flash"]}, "video-camera": {"a": "video camera", "b": "1F4F9", "j": ["camera", "video", "record", "film"]}, "videocassette": {"a": "videocassette", "b": "1F4FC", "j": ["video", "80s", "record", "tape", "vhs", "oldschool", "90s"]}, "magnifying-glass-tilted-left": {"a": "magnifying glass tilted left", "b": "1F50D", "j": ["magnifying", "find", "search", "tool", "glass", "zoom", "detective"]}, "magnifying-glass-tilted-right": {"a": "magnifying glass tilted right", "b": "1F50E", "j": ["magnifying", "find", "search", "tool", "glass", "zoom", "detective"]}, "candle": {"a": "candle", "b": "1F56F", "j": ["wax", "light", "fire"]}, "light-bulb": {"a": "light bulb", "b": "1F4A1", "j": ["electric", "bulb", "comic", "light", "electricity", "idea"]}, "flashlight": {"a": "flashlight", "b": "1F526", "j": ["night", "electric", "sight", "torch", "light", "camping", "dark", "tool"]}, "red-paper-lantern": {"a": "red paper lantern", "b": "1F3EE", "j": ["paper", "bar", "halloween", "red", "light", "spooky", "lantern"]}, "diya-lamp": {"a": "diya lamp", "b": "1FA94", "j": ["lighting", "lamp", "diya", "oil"]}, "notebook-with-decorative-cover": {"a": "notebook with decorative cover", "b": "1F4D4", "j": ["book", "paper", "decorated", "record", "study", "cover", "classroom", "notes", "notebook"]}, "closed-book": {"a": "closed book", "b": "1F4D5", "j": ["learn", "book", "library", "read", "closed", "textbook", "knowledge"]}, "open-book": {"a": "open book", "b": "1F4D6", "j": ["learn", "book", "literature", "library", "study", "open", "read", "knowledge"]}, "green-book": {"a": "green book", "b": "1F4D7", "j": ["book", "library", "study", "read", "green", "knowledge"]}, "blue-book": {"a": "blue book", "b": "1F4D8", "j": ["blue", "learn", "book", "library", "study", "read", "knowledge"]}, "orange-book": {"a": "orange book", "b": "1F4D9", "j": ["book", "library", "orange", "study", "read", "textbook", "knowledge"]}, "books": {"a": "books", "b": "1F4DA", "j": ["book", "study", "library", "literature"]}, "notebook": {"a": "notebook", "b": "1F4D3", "j": ["paper", "record", "study", "stationery", "notes"]}, "ledger": {"a": "ledger", "b": "1F4D2", "j": ["notebook", "paper", "notes"]}, "page-with-curl": {"a": "page with curl", "b": "1F4C3", "j": ["document", "page", "curl", "paper", "documents", "office"]}, "scroll": {"a": "scroll", "b": "1F4DC", "j": ["paper", "history", "documents", "ancient"]}, "page-facing-up": {"a": "page facing up", "b": "1F4C4", "j": ["document", "page", "paper", "documents", "office", "information"]}, "newspaper": {"a": "newspaper", "b": "1F4F0", "j": ["headline", "paper", "press", "news"]}, "rolledup-newspaper": {"a": "rolled-up newspaper", "b": "1F5DE", "j": ["rolled_up_newspaper", "paper", "press", "news", "headline", "rolled", "newspaper"]}, "bookmark-tabs": {"a": "bookmark tabs", "b": "1F4D1", "j": ["order", "mark", "bookmark", "save", "favorite", "marker", "tidy", "tabs"]}, "bookmark": {"a": "bookmark", "b": "1F516", "j": ["save", "label", "favorite", "mark"]}, "label": {"a": "label", "b": "1F3F7", "j": ["tag", "sale"]}, "money-bag": {"a": "money bag", "b": "1F4B0", "j": ["moneybag", "dollar", "payment", "bag", "money", "coins", "sale"]}, "coin": {"a": "coin", "b": "1FA99", "j": ["metal", "currency", "silver", "treasure", "money", "gold"]}, "yen-banknote": {"a": "yen banknote", "b": "1F4B4", "j": ["bill", "currency", "japanese", "banknote", "dollar", "note", "yen", "money", "sales"]}, "dollar-banknote": {"a": "dollar banknote", "b": "1F4B5", "j": ["bill", "currency", "banknote", "dollar", "note", "money", "sales"]}, "euro-banknote": {"a": "euro banknote", "b": "1F4B6", "j": ["bill", "currency", "euro", "banknote", "dollar", "note", "money", "sales"]}, "pound-banknote": {"a": "pound banknote", "b": "1F4B7", "j": ["bill", "england", "currency", "british", "banknote", "uk", "note", "sterling", "sales", "money", "pound", "bills"]}, "money-with-wings": {"a": "money with wings", "b": "1F4B8", "j": ["bill", "banknote", "dollar", "payment", "money", "fly", "wings", "bills", "sale"]}, "credit-card": {"a": "credit card", "b": "1F4B3", "j": ["shopping", "bill", "card", "dollar", "credit", "payment", "money", "sales"]}, "receipt": {"a": "receipt", "b": "1F9FE", "j": ["bookkeeping", "accounting", "expenses", "evidence", "proof"]}, "chart-increasing-with-yen": {"a": "chart increasing with yen", "b": "1F4B9", "j": ["stats", "graph", "growth", "chart", "yen", "money", "presentation", "green-square"]}, "envelope": {"a": "envelope", "b": "2709", "j": ["letter", "communication", "email", "inbox", "postal"]}, "email": {"a": "e-mail", "b": "1F4E7", "j": ["e_mail", "letter", "communication", "mail", "inbox"]}, "incoming-envelope": {"a": "incoming envelope", "b": "1F4E8", "j": ["envelope", "incoming", "letter", "email", "receive", "e-mail", "inbox"]}, "envelope-with-arrow": {"a": "envelope with arrow", "b": "1F4E9", "j": ["arrow", "envelope", "communication", "email", "outgoing", "e-mail"]}, "outbox-tray": {"a": "outbox tray", "b": "1F4E4", "j": ["letter", "email", "outbox", "mail", "inbox", "box", "sent", "tray"]}, "inbox-tray": {"a": "inbox tray", "b": "1F4E5", "j": ["letter", "email", "documents", "mail", "receive", "inbox", "box", "tray"]}, "package": {"a": "package", "b": "1F4E6", "j": ["moving", "gift", "mail", "cardboard", "box", "parcel"]}, "closed-mailbox-with-raised-flag": {"a": "closed mailbox with raised flag", "b": "1F4EB", "j": ["postbox", "communication", "email", "mailbox", "closed", "mail", "inbox"]}, "closed-mailbox-with-lowered-flag": {"a": "closed mailbox with lowered flag", "b": "1F4EA", "j": ["postbox", "communication", "email", "lowered", "mailbox", "closed", "mail", "inbox"]}, "open-mailbox-with-raised-flag": {"a": "open mailbox with raised flag", "b": "1F4EC", "j": ["postbox", "communication", "email", "open", "mailbox", "mail", "inbox"]}, "open-mailbox-with-lowered-flag": {"a": "open mailbox with lowered flag", "b": "1F4ED", "j": ["postbox", "email", "lowered", "open", "mailbox", "mail", "inbox"]}, "postbox": {"a": "postbox", "b": "1F4EE", "j": ["envelope", "letter", "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", "study", "school", "stationery", "writing", "write"]}, "black-nib": {"a": "black nib", "b": "2712", "j": ["stationery", "writing", "write", "nib", "pen"]}, "fountain-pen": {"a": "fountain pen", "b": "1F58B", "j": ["fountain", "stationery", "writing", "write", "pen"]}, "pen": {"a": "pen", "b": "1F58A", "j": ["ballpoint", "write", "stationery", "writing"]}, "paintbrush": {"a": "paintbrush", "b": "1F58C", "j": ["painting", "drawing", "art", "creativity"]}, "crayon": {"a": "crayon", "b": "1F58D", "j": ["creativity", "drawing"]}, "memo": {"a": "memo", "b": "1F4DD", "j": ["test", "pencil", "quiz", "compose", "paper", "documents", "study", "stationery", "writing", "write", "legal", "exam"]}, "briefcase": {"a": "briefcase", "b": "1F4BC", "j": ["job", "work", "business", "law", "documents", "career", "legal"]}, "file-folder": {"a": "file folder", "b": "1F4C1", "j": ["file", "business", "folder", "documents", "office"]}, "open-file-folder": {"a": "open file folder", "b": "1F4C2", "j": ["file", "folder", "documents", "open", "load"]}, "card-index-dividers": {"a": "card index dividers", "b": "1F5C2", "j": ["organizing", "business", "card", "stationery", "dividers", "index"]}, "calendar": {"a": "calendar", "b": "1F4C5", "j": ["date", "schedule"]}, "tearoff-calendar": {"a": "tear-off calendar", "b": "1F4C6", "j": ["date", "schedule", "calendar", "planning", "tear_off_calendar"]}, "spiral-notepad": {"a": "spiral notepad", "b": "1F5D2", "j": ["pad", "memo", "stationery", "note", "spiral"]}, "spiral-calendar": {"a": "spiral calendar", "b": "1F5D3", "j": ["pad", "date", "calendar", "schedule", "planning", "spiral"]}, "card-index": {"a": "card index", "b": "1F4C7", "j": ["business", "card", "stationery", "rolodex", "index"]}, "chart-increasing": {"a": "chart increasing", "b": "1F4C8", "j": ["stats", "upward", "business", "graph", "growth", "good", "trend", "chart", "success", "recovery", "money", "economics", "presentation", "sales"]}, "chart-decreasing": {"a": "chart decreasing", "b": "1F4C9", "j": ["stats", "business", "failure", "recession", "graph", "down", "bad", "trend", "chart", "money", "economics", "presentation", "sales"]}, "bar-chart": {"a": "bar chart", "b": "1F4CA", "j": ["stats", "graph", "bar", "chart", "presentation"]}, "clipboard": {"a": "clipboard", "b": "1F4CB", "j": ["documents", "stationery"]}, "pushpin": {"a": "pushpin", "b": "1F4CC", "j": ["here", "pin", "stationery", "mark"]}, "round-pushpin": {"a": "round pushpin", "b": "1F4CD", "j": ["pin", "map", "pushpin", "location", "stationery", "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": ["architect", "math", "school", "stationery", "length", "drawing", "sketch", "calculate", "straight edge", "ruler"]}, "triangular-ruler": {"a": "triangular ruler", "b": "1F4D0", "j": ["architect", "math", "triangle", "stationery", "set", "sketch", "ruler"]}, "scissors": {"a": "scissors", "b": "2702", "j": ["cutting", "tool", "stationery", "cut"]}, "card-file-box": {"a": "card file box", "b": "1F5C3", "j": ["business", "file", "card", "stationery", "box"]}, "file-cabinet": {"a": "file cabinet", "b": "1F5C4", "j": ["file", "cabinet", "organizing", "filing"]}, "wastebasket": {"a": "wastebasket", "b": "1F5D1", "j": ["rubbish", "toss", "garbage", "trash", "bin"]}, "locked": {"a": "locked", "b": "1F512", "j": ["password", "padlock", "closed", "security"]}, "unlocked": {"a": "unlocked", "b": "1F513", "j": ["privacy", "open", "unlock", "lock", "security"]}, "locked-with-pen": {"a": "locked with pen", "b": "1F50F", "j": ["secret", "privacy", "lock", "nib", "security", "ink", "pen"]}, "locked-with-key": {"a": "locked with key", "b": "1F510", "j": ["privacy", "lock", "key", "closed", "security", "secure"]}, "key": {"a": "key", "b": "1F511", "j": ["password", "door", "lock"]}, "old-key": {"a": "old key", "b": "1F5DD", "j": ["door", "clue", "lock", "key", "old", "password"]}, "hammer": {"a": "hammer", "b": "1F528", "j": ["tool", "tools", "build", "create"]}, "axe": {"a": "axe", "b": "1FA93", "j": ["wood", "split", "chop", "tool", "hatchet", "cut"]}, "pick": {"a": "pick", "b": "26CF", "j": ["tool", "dig", "mining", "tools"]}, "hammer-and-pick": {"a": "hammer and pick", "b": "2692", "j": ["hammer", "build", "pick", "tools", "tool", "create"]}, "hammer-and-wrench": {"a": "hammer and wrench", "b": "1F6E0", "j": ["hammer", "build", "spanner", "tools", "tool", "wrench", "create"]}, "dagger": {"a": "dagger", "b": "1F5E1", "j": ["weapon", "knife"]}, "crossed-swords": {"a": "crossed swords", "b": "2694", "j": ["weapon", "crossed", "swords"]}, "water-pistol": {"a": "water pistol", "b": "1F52B", "j": ["violence", "tool", "water", "revolver", "weapon", "gun", "pistol", "handgun"]}, "boomerang": {"a": "boomerang", "b": "1FA83", "j": ["rebound", "australia", "repercussion", "weapon"]}, "bow-and-arrow": {"a": "bow and arrow", "b": "1F3F9", "j": ["arrow", "archer", "zodiac", "sports", "Sagittarius", "bow"]}, "shield": {"a": "shield", "b": "1F6E1", "j": ["weapon", "protection", "security"]}, "carpentry-saw": {"a": "carpentry saw", "b": "1FA9A", "j": ["carpenter", "lumber", "saw", "chop", "tool", "cut"]}, "wrench": {"a": "wrench", "b": "1F527", "j": ["diy", "ikea", "spanner", "tools", "fix", "maintainer", "tool"]}, "screwdriver": {"a": "screwdriver", "b": "1FA9B", "j": ["tool", "screw", "tools"]}, "nut-and-bolt": {"a": "nut and bolt", "b": "1F529", "j": ["handy", "tools", "fix", "nut", "tool", "bolt"]}, "gear": {"a": "gear", "b": "2699", "j": ["cog", "cogwheel", "tool"]}, "clamp": {"a": "clamp", "b": "1F5DC", "j": ["vice", "tool", "compress"]}, "balance-scale": {"a": "balance scale", "b": "2696", "j": ["balance", "Libra", "law", "zodiac", "fairness", "justice", "scale", "weight"]}, "white-cane": {"a": "white cane", "b": "1F9AF", "j": ["blind", "probing_cane", "accessibility"]}, "link": {"a": "link", "b": "1F517", "j": ["url", "rings"]}, "chains": {"a": "chains", "b": "26D3", "j": ["arrest", "lock", "chain"]}, "hook": {"a": "hook", "b": "1FA9D", "j": ["crook", "curve", "catch", "selling point", "tools", "ensnare"]}, "toolbox": {"a": "toolbox", "b": "1F9F0", "j": ["chest", "diy", "tools", "fix", "maintainer", "mechanic", "tool"]}, "magnet": {"a": "magnet", "b": "1F9F2", "j": ["attraction", "magnetic", "horseshoe"]}, "ladder": {"a": "ladder", "b": "1FA9C", "j": ["rung", "step", "tools", "climb"]}, "alembic": {"a": "alembic", "b": "2697", "j": ["chemistry", "distilling", "experiment", "science", "tool"]}, "test-tube": {"a": "test tube", "b": "1F9EA", "j": ["chemistry", "experiment", "lab", "science", "chemist"]}, "petri-dish": {"a": "petri dish", "b": "1F9EB", "j": ["biologist", "culture", "biology", "lab", "bacteria"]}, "dna": {"a": "dna", "b": "1F9EC", "j": ["biologist", "life", "gene", "evolution", "genetics"]}, "microscope": {"a": "microscope", "b": "1F52C", "j": ["study", "experiment", "laboratory", "science", "tool", "zoomin"]}, "telescope": {"a": "telescope", "b": "1F52D", "j": ["stars", "astronomy", "space", "science", "tool", "zoom"]}, "satellite-antenna": {"a": "satellite antenna", "b": "1F4E1", "j": ["communication", "satellite", "space", "antenna", "dish", "future", "radio"]}, "syringe": {"a": "syringe", "b": "1F489", "j": ["nurse", "shot", "doctor", "medicine", "hospital", "health", "sick", "needle", "drugs", "blood"]}, "drop-of-blood": {"a": "drop of blood", "b": "1FA78", "j": ["bleed", "menstruation", "harm", "medicine", "wound", "period", "blood donation", "injury", "hurt"]}, "pill": {"a": "pill", "b": "1F48A", "j": ["drug", "doctor", "medicine", "health", "sick", "pharmacy"]}, "adhesive-bandage": {"a": "adhesive bandage", "b": "1FA79", "j": ["heal", "bandage"]}, "stethoscope": {"a": "stethoscope", "b": "1FA7A", "j": ["heart", "medicine", "doctor", "health"]}, "door": {"a": "door", "b": "1F6AA", "j": ["house", "entry", "exit"]}, "elevator": {"a": "elevator", "b": "1F6D7", "j": ["lift", "hoist", "accessibility"]}, "mirror": {"a": "mirror", "b": "1FA9E", "j": ["reflection", "reflector", "speculum"]}, "window": {"a": "window", "b": "1FA9F", "j": ["transparent", "fresh air", "opening", "view", "scenery", "frame"]}, "bed": {"a": "bed", "b": "1F6CF", "j": ["sleep", "hotel", "rest"]}, "couch-and-lamp": {"a": "couch and lamp", "b": "1F6CB", "j": ["lamp", "chill", "couch", "read", "hotel"]}, "chair": {"a": "chair", "b": "1FA91", "j": ["sit", "furniture", "seat"]}, "toilet": {"a": "toilet", "b": "1F6BD", "j": ["potty", "bathroom", "washroom", "restroom", "wc"]}, "plunger": {"a": "plunger", "b": "1FAA0", "j": ["suction", "plumber", "force cup", "toilet"]}, "shower": {"a": "shower", "b": "1F6BF", "j": ["bathroom", "water", "clean"]}, "bathtub": {"a": "bathtub", "b": "1F6C1", "j": ["shower", "bathroom", "bath", "clean"]}, "mouse-trap": {"a": "mouse trap", "b": "1FAA4", "j": ["trap", "cheese", "bait", "mousetrap", "snare"]}, "razor": {"a": "razor", "b": "1FA92", "j": ["shave", "sharp", "cut"]}, "lotion-bottle": {"a": "lotion bottle", "b": "1F9F4", "j": ["sunscreen", "shampoo", "lotion", "moisturizer"]}, "safety-pin": {"a": "safety pin", "b": "1F9F7", "j": ["punk rock", "diaper"]}, "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": ["toilet paper", "roll", "paper towels"]}, "bucket": {"a": "bucket", "b": "1FAA3", "j": ["cask", "water", "pail", "vat", "container"]}, "soap": {"a": "soap", "b": "1F9FC", "j": ["soapdish", "bar", "lather", "bathing", "cleaning"]}, "toothbrush": {"a": "toothbrush", "b": "1FAA5", "j": ["brush", "bathroom", "hygiene", "teeth", "clean", "dental"]}, "sponge": {"a": "sponge", "b": "1F9FD", "j": ["absorbing", "cleaning", "porous"]}, "fire-extinguisher": {"a": "fire extinguisher", "b": "1F9EF", "j": ["quench", "extinguish", "fire"]}, "shopping-cart": {"a": "shopping cart", "b": "1F6D2", "j": ["shopping", "cart", "trolley"]}, "cigarette": {"a": "cigarette", "b": "1F6AC", "j": ["smoke", "smoking", "kills", "tobacco", "joint"]}, "coffin": {"a": "coffin", "b": "26B0", "j": ["death", "vampire", "rip", "casket", "funeral", "die", "dead", "box", "cemetery", "graveyard"]}, "headstone": {"a": "headstone", "b": "1FAA6", "j": ["death", "rip", "tombstone", "grave", "cemetery", "graveyard"]}, "funeral-urn": {"a": "funeral urn", "b": "26B1", "j": ["death", "rip", "funeral", "ashes", "die", "dead", "urn"]}, "moai": {"a": "moai", "b": "1F5FF", "j": ["rock", "moyai", "face", "statue", "easter island"]}, "placard": {"a": "placard", "b": "1FAA7", "j": ["sign", "announcement", "picket", "protest", "demonstration"]}, "atm-sign": {"a": "ATM sign", "b": "1F3E7", "j": ["cash", "bank", "teller", "automated", "atm", "blue-square", "payment", "money", "sales"]}, "litter-in-bin-sign": {"a": "litter in bin sign", "b": "1F6AE", "j": ["info", "sign", "litter", "human", "blue-square", "litter bin"]}, "potable-water": {"a": "potable water", "b": "1F6B0", "j": ["restroom", "water", "drinking", "blue-square", "liquid", "potable", "faucet", "cleaning"]}, "wheelchair-symbol": {"a": "wheelchair symbol", "b": "267F", "j": ["blue-square", "access", "disabled", "accessibility"]}, "mens-room": {"a": "men’s room", "b": "1F6B9", "j": ["gender", "man", "male", "restroom", "blue-square", "lavatory", "toilet", "men_s_room", "wc"]}, "womens-room": {"a": "women’s room", "b": "1F6BA", "j": ["gender", "woman", "restroom", "purple-square", "loo", "lavatory", "women_s_room", "toilet", "wc", "female"]}, "restroom": {"a": "restroom", "b": "1F6BB", "j": ["gender", "WC", "blue-square", "lavatory", "toilet", "refresh", "wc"]}, "baby-symbol": {"a": "baby symbol", "b": "1F6BC", "j": ["changing", "orange-square", "baby", "child"]}, "water-closet": {"a": "water closet", "b": "1F6BE", "j": ["restroom", "water", "blue-square", "lavatory", "closet", "toilet", "wc"]}, "passport-control": {"a": "passport control", "b": "1F6C2", "j": ["custom", "blue-square", "control", "passport"]}, "customs": {"a": "customs", "b": "1F6C3", "j": ["blue-square", "border", "passport"]}, "baggage-claim": {"a": "baggage claim", "b": "1F6C4", "j": ["transport", "claim", "baggage", "blue-square", "airport"]}, "left-luggage": {"a": "left luggage", "b": "1F6C5", "j": ["locker", "luggage", "baggage", "blue-square", "travel"]}, "warning": {"a": "warning", "b": "26A0", "j": ["alert", "wip", "problem", "error", "issue", "exclamation"]}, "children-crossing": {"a": "children crossing", "b": "1F6B8", "j": ["danger", "warning", "sign", "crossing", "yellow-diamond", "traffic", "pedestrian", "school", "child", "driving"]}, "no-entry": {"a": "no entry", "b": "26D4", "j": ["forbidden", "circle", "denied", "privacy", "stop", "no", "entry", "prohibited", "traffic", "bad", "not", "limit", "security"]}, "prohibited": {"a": "prohibited", "b": "1F6AB", "j": ["forbidden", "circle", "denied", "stop", "no", "entry", "disallow", "not", "limit", "forbid"]}, "no-bicycles": {"a": "no bicycles", "b": "1F6B3", "j": ["forbidden", "circle", "no", "prohibited", "bicycle", "bike", "cyclist"]}, "no-smoking": {"a": "no smoking", "b": "1F6AD", "j": ["smoke", "forbidden", "smoking", "smell", "no", "prohibited", "cigarette", "blue-square", "not"]}, "no-littering": {"a": "no littering", "b": "1F6AF", "j": ["forbidden", "circle", "litter", "no", "prohibited", "garbage", "not", "trash", "bin"]}, "nonpotable-water": {"a": "non-potable water", "b": "1F6B1", "j": ["non_potable_water", "circle", "non-drinking", "drink", "water", "faucet", "tap", "non-potable"]}, "no-pedestrians": {"a": "no pedestrians", "b": "1F6B7", "j": ["forbidden", "circle", "crossing", "no", "prohibited", "pedestrian", "walking", "not", "rules"]}, "no-mobile-phones": {"a": "no mobile phones", "b": "1F4F5", "j": ["forbidden", "circle", "iphone", "phone", "no", "mute", "mobile", "cell"]}, "no-one-under-eighteen": {"a": "no one under eighteen", "b": "1F51E", "j": ["pub", "underage", "night", "circle", "prohibited", "drink", "18", "eighteen", "age restriction", "minor"]}, "radioactive": {"a": "radioactive", "b": "2622", "j": ["danger", "sign", "nuclear"]}, "biohazard": {"a": "biohazard", "b": "2623", "j": ["danger", "sign"]}, "up-arrow": {"a": "up arrow", "b": "2B06", "j": ["arrow", "top", "blue-square", "continue", "direction", "north", "cardinal"]}, "upright-arrow": {"a": "up-right arrow", "b": "2197", "j": ["arrow", "up_right_arrow", "point", "diagonal", "blue-square", "direction", "northeast", "intercardinal"]}, "right-arrow": {"a": "right arrow", "b": "27A1", "j": ["arrow", "east", "blue-square", "direction", "next", "cardinal"]}, "downright-arrow": {"a": "down-right arrow", "b": "2198", "j": ["arrow", "diagonal", "blue-square", "down_right_arrow", "direction", "intercardinal", "southeast"]}, "down-arrow": {"a": "down arrow", "b": "2B07", "j": ["arrow", "down", "blue-square", "cardinal", "direction", "bottom", "south"]}, "downleft-arrow": {"a": "down-left arrow", "b": "2199", "j": ["arrow", "southwest", "diagonal", "blue-square", "down_left_arrow", "direction", "intercardinal"]}, "left-arrow": {"a": "left arrow", "b": "2B05", "j": ["arrow", "west", "blue-square", "previous", "back", "direction", "cardinal"]}, "upleft-arrow": {"a": "up-left arrow", "b": "2196", "j": ["arrow", "point", "northwest", "up_left_arrow", "diagonal", "blue-square", "direction", "intercardinal"]}, "updown-arrow": {"a": "up-down arrow", "b": "2195", "j": ["arrow", "blue-square", "vertical", "direction", "up_down_arrow", "way"]}, "leftright-arrow": {"a": "left-right arrow", "b": "2194", "j": ["left_right_arrow", "arrow", "sideways", "shape", "direction", "horizontal"]}, "right-arrow-curving-left": {"a": "right arrow curving left", "b": "21A9", "j": ["arrow", "undo", "back", "return", "blue-square", "enter"]}, "left-arrow-curving-right": {"a": "left arrow curving right", "b": "21AA", "j": ["arrow", "blue-square", "return", "direction", "rotate"]}, "right-arrow-curving-up": {"a": "right arrow curving up", "b": "2934", "j": ["blue-square", "arrow", "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", "sync", "round", "cycle", "repeat", "reload", "clockwise"]}, "counterclockwise-arrows-button": {"a": "counterclockwise arrows button", "b": "1F504", "j": ["arrow", "sync", "blue-square", "cycle", "anticlockwise", "withershins", "counterclockwise"]}, "back-arrow": {"a": "BACK arrow", "b": "1F519", "j": ["back", "arrow", "return", "words"]}, "end-arrow": {"a": "END arrow", "b": "1F51A", "j": ["end", "arrow", "words"]}, "on-arrow": {"a": "ON! arrow", "b": "1F51B", "j": ["arrow", "words", "on", "mark"]}, "soon-arrow": {"a": "SOON arrow", "b": "1F51C", "j": ["arrow", "soon", "words"]}, "top-arrow": {"a": "TOP arrow", "b": "1F51D", "j": ["arrow", "top", "words", "blue-square", "up"]}, "place-of-worship": {"a": "place of worship", "b": "1F6D0", "j": ["church", "worship", "prayer", "temple", "religion"]}, "atom-symbol": {"a": "atom symbol", "b": "269B", "j": ["chemistry", "atom", "atheist", "science", "physics"]}, "om": {"a": "om", "b": "1F549", "j": ["sikhism", "Hindu", "hinduism", "buddhism", "jainism", "religion"]}, "star-of-david": {"a": "star of David", "b": "2721", "j": ["star", "Jew", "David", "Jewish", "judaism", "religion"]}, "wheel-of-dharma": {"a": "wheel of dharma", "b": "2638", "j": ["dharma", "sikhism", "wheel", "hinduism", "buddhism", "jainism", "Buddhist", "religion"]}, "yin-yang": {"a": "yin yang", "b": "262F", "j": ["balance", "yang", "tao", "taoist", "yin", "religion"]}, "latin-cross": {"a": "latin cross", "b": "271D", "j": ["religion", "cross", "christianity", "Christian"]}, "orthodox-cross": {"a": "orthodox cross", "b": "2626", "j": ["cross", "religion", "suppedaneum", "Christian"]}, "star-and-crescent": {"a": "star and crescent", "b": "262A", "j": ["islam", "Muslim", "religion"]}, "peace-symbol": {"a": "peace symbol", "b": "262E", "j": ["hippie", "peace"]}, "menorah": {"a": "menorah", "b": "1F54E", "j": ["candlestick", "hanukkah", "jewish", "candelabrum", "candles", "religion"]}, "dotted-sixpointed-star": {"a": "dotted six-pointed star", "b": "1F52F", "j": ["jewish", "star", "purple-square", "dotted_six_pointed_star", "fortune", "religion", "hexagram"]}, "aries": {"a": "Aries", "b": "2648", "j": ["astrology", "sign", "ram", "purple-square", "zodiac"]}, "taurus": {"a": "Taurus", "b": "2649", "j": ["astrology", "sign", "purple-square", "ox", "zodiac", "bull"]}, "gemini": {"a": "Gemini", "b": "264A", "j": ["astrology", "sign", "twins", "purple-square", "zodiac"]}, "cancer": {"a": "Cancer", "b": "264B", "j": ["astrology", "sign", "purple-square", "zodiac", "crab"]}, "leo": {"a": "Leo", "b": "264C", "j": ["astrology", "sign", "lion", "purple-square", "zodiac"]}, "virgo": {"a": "Virgo", "b": "264D", "j": ["astrology", "sign", "purple-square", "zodiac"]}, "libra": {"a": "Libra", "b": "264E", "j": ["astrology", "sign", "balance", "purple-square", "zodiac", "scales", "justice"]}, "scorpio": {"a": "Scorpio", "b": "264F", "j": ["astrology", "sign", "scorpion", "purple-square", "zodiac", "scorpius"]}, "sagittarius": {"a": "Sagittarius", "b": "2650", "j": ["astrology", "sign", "archer", "purple-square", "zodiac"]}, "capricorn": {"a": "Capricorn", "b": "2651", "j": ["astrology", "sign", "purple-square", "zodiac", "goat"]}, "aquarius": {"a": "Aquarius", "b": "2652", "j": ["astrology", "sign", "purple-square", "water", "zodiac", "bearer"]}, "pisces": {"a": "Pisces", "b": "2653", "j": ["astrology", "sign", "purple-square", "fish", "zodiac"]}, "ophiuchus": {"a": "Ophiuchus", "b": "26CE", "j": ["astrology", "sign", "serpent", "snake", "purple-square", "zodiac", "constellation", "bearer"]}, "shuffle-tracks-button": {"a": "shuffle tracks button", "b": "1F500", "j": ["arrow", "random", "crossed", "blue-square", "shuffle", "music"]}, "repeat-button": {"a": "repeat button", "b": "1F501", "j": ["arrow", "record", "loop", "repeat", "clockwise"]}, "repeat-single-button": {"a": "repeat single button", "b": "1F502", "j": ["arrow", "once", "blue-square", "loop", "clockwise"]}, "play-button": {"a": "play button", "b": "25B6", "j": ["arrow", "play", "triangle", "blue-square", "direction", "right"]}, "fastforward-button": {"a": "fast-forward button", "b": "23E9", "j": ["arrow", "play", "fast", "fast_forward_button", "blue-square", "speed", "continue", "double", "forward"]}, "next-track-button": {"a": "next track button", "b": "23ED", "j": ["arrow", "next track", "next scene", "triangle", "blue-square", "next", "forward"]}, "play-or-pause-button": {"a": "play or pause button", "b": "23EF", "j": ["arrow", "play", "triangle", "blue-square", "pause", "right"]}, "reverse-button": {"a": "reverse button", "b": "25C0", "j": ["arrow", "reverse", "triangle", "left", "blue-square", "direction"]}, "fast-reverse-button": {"a": "fast reverse button", "b": "23EA", "j": ["arrow", "play", "blue-square", "double", "rewind"]}, "last-track-button": {"a": "last track button", "b": "23EE", "j": ["previous track", "arrow", "previous scene", "triangle", "backward"]}, "upwards-button": {"a": "upwards button", "b": "1F53C", "j": ["arrow", "top", "point", "triangle", "blue-square", "red", "direction", "button", "forward"]}, "fast-up-button": {"a": "fast up button", "b": "23EB", "j": ["arrow", "top", "blue-square", "direction", "double"]}, "downwards-button": {"a": "downwards button", "b": "1F53D", "j": ["arrow", "down", "blue-square", "red", "direction", "button", "bottom"]}, "fast-down-button": {"a": "fast down button", "b": "23EC", "j": ["arrow", "down", "blue-square", "direction", "double", "bottom"]}, "pause-button": {"a": "pause button", "b": "23F8", "j": ["bar", "blue-square", "vertical", "double", "pause"]}, "stop-button": {"a": "stop button", "b": "23F9", "j": ["blue-square", "square", "stop"]}, "record-button": {"a": "record button", "b": "23FA", "j": ["blue-square", "circle", "record"]}, "eject-button": {"a": "eject button", "b": "23CF", "j": ["blue-square", "eject"]}, "cinema": {"a": "cinema", "b": "1F3A6", "j": ["theater", "stage", "camera", "record", "curtain", "film", "blue-square", "movie"]}, "dim-button": {"a": "dim button", "b": "1F505", "j": ["sun", "brightness", "low", "warm", "afternoon", "dim", "summer"]}, "bright-button": {"a": "bright button", "b": "1F506", "j": ["sun", "brightness", "light", "bright"]}, "antenna-bars": {"a": "antenna bars", "b": "1F4F6", "j": ["phone", "bars", "bluetooth", "bar", "mobile", "blue-square", "internet", "antenna", "connection", "wifi", "cell", "reception"]}, "vibration-mode": {"a": "vibration mode", "b": "1F4F3", "j": ["phone", "orange-square", "telephone", "mobile", "vibration", "mode", "cell"]}, "mobile-phone-off": {"a": "mobile phone off", "b": "1F4F4", "j": ["silence", "phone", "orange-square", "telephone", "mute", "mobile", "quiet", "cell", "off"]}, "female-sign": {"a": "female sign", "b": "2640", "j": ["lady", "women", "girl", "woman"]}, "male-sign": {"a": "male sign", "b": "2642", "j": ["boy", "man", "men"]}, "transgender-symbol": {"a": "transgender symbol", "b": "26A7", "j": ["transgender", "lgbtq"]}, "multiply": {"a": "multiply", "b": "2716", "j": ["sign", "multiplication_sign", "math", "cancel", "multiplication", "calculation", "x", "×"]}, "plus": {"a": "plus", "b": "2795", "j": ["sign", "+", "increase", "math", "more", "calculation", "plus_sign", "addition"]}, "minus": {"a": "minus", "b": "2796", "j": ["minus_sign", "sign", "math", "−", "calculation", "-", "less", "subtract"]}, "divide": {"a": "divide", "b": "2797", "j": ["sign", "division_sign", "math", "÷", "division", "calculation"]}, "infinity": {"a": "infinity", "b": "267E", "j": ["unbounded", "universal", "forever"]}, "double-exclamation-mark": {"a": "double exclamation mark", "b": "203C", "j": ["!!", "mark", "bangbang", "exclamation", "!", "surprise"]}, "exclamation-question-mark": {"a": "exclamation question mark", "b": "2049", "j": ["punctuation", "interrobang", "mark", "exclamation", "?", "wat", "!?", "question", "!", "surprise"]}, "red-question-mark": {"a": "red question mark", "b": "2753", "j": ["punctuation", "question_mark", "mark", "?", "question", "confused", "doubt"]}, "white-question-mark": {"a": "white question mark", "b": "2754", "j": ["confused", "punctuation", "mark", "?", "outlined", "huh", "doubts", "question", "gray"]}, "white-exclamation-mark": {"a": "white exclamation mark", "b": "2755", "j": ["warning", "punctuation", "mark", "wow", "outlined", "exclamation", "!", "surprise", "gray"]}, "red-exclamation-mark": {"a": "red exclamation mark", "b": "2757", "j": ["danger", "warning", "punctuation", "heavy_exclamation_mark", "mark", "exclamation_mark", "wow", "exclamation", "!", "surprise"]}, "wavy-dash": {"a": "wavy dash", "b": "3030", "j": ["moustache", "punctuation", "draw", "dash", "squiggle", "scribble", "mustache", "line", "wavy"]}, "currency-exchange": {"a": "currency exchange", "b": "1F4B1", "j": ["currency", "bank", "dollar", "travel", "exchange", "money", "sales"]}, "heavy-dollar-sign": {"a": "heavy dollar sign", "b": "1F4B2", "j": ["currency", "dollar", "payment", "money", "sales", "buck"]}, "medical-symbol": {"a": "medical symbol", "b": "2695", "j": ["staff", "aesculapius", "hospital", "medicine", "health"]}, "recycling-symbol": {"a": "recycling symbol", "b": "267B", "j": ["arrow", "recycle", "environment", "garbage", "trash"]}, "fleurdelis": {"a": "fleur-de-lis", "b": "269C", "j": ["fleur_de_lis", "decorative", "scout"]}, "trident-emblem": {"a": "trident emblem", "b": "1F531", "j": ["emblem", "anchor", "weapon", "ship", "tool", "spear", "trident"]}, "name-badge": {"a": "name badge", "b": "1F4DB", "j": ["name", "forbid", "fire", "badge"]}, "japanese-symbol-for-beginner": {"a": "Japanese symbol for beginner", "b": "1F530", "j": ["chevron", "badge", "leaf", "shield", "Japanese", "beginner"]}, "hollow-red-circle": {"a": "hollow red circle", "b": "2B55", "j": ["large", "circle", "o", "red", "round"]}, "check-mark-button": {"a": "check mark button", "b": "2705", "j": ["election", "check", "✓", "green-square", "mark", "vote", "agree", "answer", "tick", "button", "ok"]}, "check-box-with-check": {"a": "check box with check", "b": "2611", "j": ["election", "yes", "check", "✓", "black-square", "vote", "confirm", "agree", "tick", "box", "ok"]}, "check-mark": {"a": "check mark", "b": "2714", "j": ["yes", "check", "✓", "nike", "mark", "answer", "tick", "ok"]}, "cross-mark": {"a": "cross mark", "b": "274C", "j": ["cross", "mark", "no", "delete", "cancel", "multiply", "remove", "multiplication", "red", "x", "×"]}, "cross-mark-button": {"a": "cross mark button", "b": "274E", "j": ["mark", "no", "square", "deny", "x", "green-square", "×"]}, "curly-loop": {"a": "curly loop", "b": "27B0", "j": ["draw", "curl", "shape", "squiggle", "scribble", "loop"]}, "double-curly-loop": {"a": "double curly loop", "b": "27BF", "j": ["curl", "tape", "cassette", "double", "loop"]}, "part-alternation-mark": {"a": "part alternation mark", "b": "303D", "j": ["stats", "business", "mark", "graph", "bad", "part", "economics", "presentation"]}, "eightspoked-asterisk": {"a": "eight-spoked asterisk", "b": "2733", "j": ["eight_spoked_asterisk", "star", "*", "sparkle", "asterisk", "green-square"]}, "eightpointed-star": {"a": "eight-pointed star", "b": "2734", "j": ["polygon", "orange-square", "star", "*", "shape", "eight_pointed_star"]}, "sparkle": {"a": "sparkle", "b": "2747", "j": ["awesome", "fireworks", "good", "stars", "*", "green-square"]}, "copyright": {"a": "copyright", "b": "00A9", "j": ["ip", "circle", "license", "law", "legal", "c"]}, "registered": {"a": "registered", "b": "00AE", "j": ["alphabet", "circle", "r"]}, "trade-mark": {"a": "trade mark", "b": "2122", "j": ["mark", "tm", "law", "brand", "legal", "trademark"]}, "keycap": {"a": "keycap: *", "b": "002A-FE0F-20E3", "j": ["star", "keycap_"]}, "keycap-0": {"a": "keycap: 0", "b": "0030-FE0F-20E3", "j": ["0", "keycap", "numbers", "blue-square", "null"]}, "keycap-1": {"a": "keycap: 1", "b": "0031-FE0F-20E3", "j": ["numbers", "blue-square", "1", "keycap"]}, "keycap-2": {"a": "keycap: 2", "b": "0032-FE0F-20E3", "j": ["keycap", "numbers", "2", "blue-square", "prime"]}, "keycap-3": {"a": "keycap: 3", "b": "0033-FE0F-20E3", "j": ["keycap", "numbers", "3", "blue-square", "prime"]}, "keycap-4": {"a": "keycap: 4", "b": "0034-FE0F-20E3", "j": ["numbers", "blue-square", "4", "keycap"]}, "keycap-5": {"a": "keycap: 5", "b": "0035-FE0F-20E3", "j": ["keycap", "numbers", "blue-square", "prime", "5"]}, "keycap-6": {"a": "keycap: 6", "b": "0036-FE0F-20E3", "j": ["numbers", "6", "keycap", "blue-square"]}, "keycap-7": {"a": "keycap: 7", "b": "0037-FE0F-20E3", "j": ["7", "keycap", "numbers", "blue-square", "prime"]}, "keycap-8": {"a": "keycap: 8", "b": "0038-FE0F-20E3", "j": ["numbers", "blue-square", "8", "keycap"]}, "keycap-9": {"a": "keycap: 9", "b": "0039-FE0F-20E3", "j": ["numbers", "blue-square", "9", "keycap"]}, "keycap-10": {"a": "keycap: 10", "b": "1F51F", "j": ["numbers", "blue-square", "10", "keycap"]}, "input-latin-uppercase": {"a": "input latin uppercase", "b": "1F520", "j": ["latin", "words", "alphabet", "input", "ABCD", "uppercase", "blue-square", "letters"]}, "input-latin-lowercase": {"a": "input latin lowercase", "b": "1F521", "j": ["latin", "alphabet", "abcd", "input", "blue-square", "letters", "lowercase"]}, "input-numbers": {"a": "input numbers", "b": "1F522", "j": ["numbers", "blue-square", "1234", "input"]}, "input-symbols": {"a": "input symbols", "b": "1F523", "j": ["percent", "〒♪&%", "ampersand", "input", "blue-square", "note", "glyphs", "characters", "music"]}, "input-latin-letters": {"a": "input latin letters", "b": "1F524", "j": ["latin", "alphabet", "input", "blue-square", "abc", "letters"]}, "a-button-blood-type": {"a": "A button (blood type)", "b": "1F170", "j": ["a_button", "letter", "alphabet", "a", "red-square", "blood type"]}, "ab-button-blood-type": {"a": "AB button (blood type)", "b": "1F18E", "j": ["ab_button", "alphabet", "red-square", "ab", "blood type"]}, "b-button-blood-type": {"a": "B button (blood type)", "b": "1F171", "j": ["letter", "alphabet", "b", "red-square", "b_button", "blood type"]}, "cl-button": {"a": "CL button", "b": "1F191", "j": ["alphabet", "red-square", "words", "cl"]}, "cool-button": {"a": "COOL button", "b": "1F192", "j": ["blue-square", "words", "cool"]}, "free-button": {"a": "FREE button", "b": "1F193", "j": ["blue-square", "free", "words"]}, "information": {"a": "information", "b": "2139", "j": ["i", "alphabet", "blue-square", "letter"]}, "id-button": {"a": "ID button", "b": "1F194", "j": ["id", "identity", "purple-square", "words"]}, "circled-m": {"a": "circled M", "b": "24C2", "j": ["circle", "blue-circle", "letter", "alphabet", "m"]}, "new-button": {"a": "NEW button", "b": "1F195", "j": ["blue-square", "start", "new", "words"]}, "ng-button": {"a": "NG button", "b": "1F196", "j": ["icon", "words", "blue-square", "ng", "shape"]}, "o-button-blood-type": {"a": "O button (blood type)", "b": "1F17E", "j": ["letter", "alphabet", "o", "o_button", "red-square", "blood type"]}, "ok-button": {"a": "OK button", "b": "1F197", "j": ["yes", "OK", "good", "agree", "blue-square"]}, "p-button": {"a": "P button", "b": "1F17F", "j": ["letter", "alphabet", "blue-square", "parking", "cars"]}, "sos-button": {"a": "SOS button", "b": "1F198", "j": ["words", "sos", "emergency", "red-square", "911", "help"]}, "up-button": {"a": "UP! button", "b": "1F199", "j": ["mark", "high", "above", "blue-square", "up"]}, "vs-button": {"a": "VS button", "b": "1F19A", "j": ["orange-square", "vs", "words", "versus"]}, "japanese-here-button": {"a": "Japanese “here” button", "b": "1F201", "j": ["destination", "ココ", "katakana", "Japanese", "japanese", "“here”", "blue-square", "here"]}, "japanese-service-charge-button": {"a": "Japanese “service charge” button", "b": "1F202", "j": ["katakana", "Japanese", "japanese", "blue-square", "“service charge”", "サ"]}, "japanese-monthly-amount-button": {"a": "Japanese “monthly amount” button", "b": "1F237", "j": ["orange-square", "moon", "Japanese", "japanese", "kanji", "ideograph", "月", "chinese", "“monthly amount”", "month"]}, "japanese-not-free-of-charge-button": {"a": "Japanese “not free of charge” button", "b": "1F236", "j": ["有", "orange-square", "Japanese", "have", "kanji", "ideograph", "chinese", "“not free of charge”"]}, "japanese-reserved-button": {"a": "Japanese “reserved” button", "b": "1F22F", "j": ["point", "“reserved”", "Japanese", "kanji", "ideograph", "chinese", "green-square", "指"]}, "japanese-bargain-button": {"a": "Japanese “bargain” button", "b": "1F250", "j": ["circle", "得", "obtain", "Japanese", "get", "“bargain”", "kanji", "ideograph", "chinese"]}, "japanese-discount-button": {"a": "Japanese “discount” button", "b": "1F239", "j": ["pink-square", "Japanese", "“discount”", "kanji", "ideograph", "割", "chinese", "divide", "cut"]}, "japanese-free-of-charge-button": {"a": "Japanese “free of charge” button", "b": "1F21A", "j": ["“free of charge”", "orange-square", "Japanese", "japanese", "nothing", "kanji", "ideograph", "chinese", "無"]}, "japanese-prohibited-button": {"a": "Japanese “prohibited” button", "b": "1F232", "j": ["“prohibited”", "forbidden", "禁", "Japanese", "japanese", "red-square", "kanji", "ideograph", "limit", "chinese", "restricted"]}, "japanese-acceptable-button": {"a": "Japanese “acceptable” button", "b": "1F251", "j": ["yes", "good", "orange-circle", "可", "Japanese", "agree", "kanji", "ideograph", "chinese", "ok", "“acceptable”"]}, "japanese-application-button": {"a": "Japanese “application” button", "b": "1F238", "j": ["orange-square", "Japanese", "japanese", "kanji", "ideograph", "“application”", "chinese", "申"]}, "japanese-passing-grade-button": {"a": "Japanese “passing grade” button", "b": "1F234", "j": ["join", "Japanese", "japanese", "“passing grade”", "red-square", "kanji", "ideograph", "chinese", "合"]}, "japanese-vacancy-button": {"a": "Japanese “vacancy” button", "b": "1F233", "j": ["“vacancy”", "空", "Japanese", "japanese", "blue-square", "sky", "kanji", "ideograph", "empty", "chinese"]}, "japanese-congratulations-button": {"a": "Japanese “congratulations” button", "b": "3297", "j": ["Japanese", "japanese", "kanji", "ideograph", "chinese", "red-circle", "“congratulations”", "祝"]}, "japanese-secret-button": {"a": "Japanese “secret” button", "b": "3299", "j": ["privacy", "“secret”", "Japanese", "sshh", "kanji", "ideograph", "秘", "red-circle", "chinese"]}, "japanese-open-for-business-button": {"a": "Japanese “open for business” button", "b": "1F23A", "j": ["orange-square", "Japanese", "営", "japanese", "opening hours", "“open for business”", "ideograph"]}, "japanese-no-vacancy-button": {"a": "Japanese “no vacancy” button", "b": "1F235", "j": ["満", "full", "Japanese", "japanese", "red-square", "kanji", "ideograph", "chinese", "“no vacancy”"]}, "red-circle": {"a": "red circle", "b": "1F534", "j": ["danger", "circle", "shape", "red", "geometric", "error"]}, "orange-circle": {"a": "orange circle", "b": "1F7E0", "j": ["circle", "orange", "round"]}, "yellow-circle": {"a": "yellow circle", "b": "1F7E1", "j": ["circle", "round", "yellow"]}, "green-circle": {"a": "green circle", "b": "1F7E2", "j": ["circle", "round", "green"]}, "blue-circle": {"a": "blue circle", "b": "1F535", "j": ["blue", "circle", "icon", "geometric", "shape", "button"]}, "purple-circle": {"a": "purple circle", "b": "1F7E3", "j": ["circle", "purple", "round"]}, "brown-circle": {"a": "brown circle", "b": "1F7E4", "j": ["circle", "brown", "round"]}, "black-circle": {"a": "black circle", "b": "26AB", "j": ["circle", "shape", "geometric", "button", "round"]}, "white-circle": {"a": "white circle", "b": "26AA", "j": ["circle", "shape", "round", "geometric"]}, "red-square": {"a": "red square", "b": "1F7E5", "j": ["red", "square"]}, "orange-square": {"a": "orange square", "b": "1F7E7", "j": ["square", "orange"]}, "yellow-square": {"a": "yellow square", "b": "1F7E8", "j": ["square", "yellow"]}, "green-square": {"a": "green square", "b": "1F7E9", "j": ["square", "green"]}, "blue-square": {"a": "blue square", "b": "1F7E6", "j": ["blue", "square"]}, "purple-square": {"a": "purple square", "b": "1F7EA", "j": ["square", "purple"]}, "brown-square": {"a": "brown square", "b": "1F7EB", "j": ["square", "brown"]}, "black-large-square": {"a": "black large square", "b": "2B1B", "j": ["icon", "geometric", "shape", "square", "button"]}, "white-large-square": {"a": "white large square", "b": "2B1C", "j": ["icon", "stone", "shape", "geometric", "square", "button"]}, "black-medium-square": {"a": "black medium square", "b": "25FC", "j": ["icon", "geometric", "shape", "square", "button"]}, "white-medium-square": {"a": "white medium square", "b": "25FB", "j": ["icon", "stone", "shape", "geometric", "square"]}, "black-mediumsmall-square": {"a": "black medium-small square", "b": "25FE", "j": ["icon", "geometric", "shape", "square", "black_medium_small_square", "button"]}, "white-mediumsmall-square": {"a": "white medium-small square", "b": "25FD", "j": ["icon", "stone", "geometric", "shape", "square", "white_medium_small_square", "button"]}, "black-small-square": {"a": "black small square", "b": "25AA", "j": ["geometric", "shape", "icon", "square"]}, "white-small-square": {"a": "white small square", "b": "25AB", "j": ["geometric", "shape", "icon", "square"]}, "large-orange-diamond": {"a": "large orange diamond", "b": "1F536", "j": ["diamond", "orange", "gem", "shape", "geometric", "jewel"]}, "large-blue-diamond": {"a": "large blue diamond", "b": "1F537", "j": ["blue", "diamond", "geometric", "shape", "gem", "jewel"]}, "small-orange-diamond": {"a": "small orange diamond", "b": "1F538", "j": ["diamond", "orange", "gem", "shape", "geometric", "jewel"]}, "small-blue-diamond": {"a": "small blue diamond", "b": "1F539", "j": ["blue", "diamond", "geometric", "shape", "gem", "jewel"]}, "red-triangle-pointed-up": {"a": "red triangle pointed up", "b": "1F53A", "j": ["top", "geometric", "shape", "red", "direction", "up"]}, "red-triangle-pointed-down": {"a": "red triangle pointed down", "b": "1F53B", "j": ["down", "shape", "red", "geometric", "direction", "bottom"]}, "diamond-with-a-dot": {"a": "diamond with a dot", "b": "1F4A0", "j": ["blue", "diamond", "fancy", "inside", "crystal", "comic", "geometric", "gem", "jewel"]}, "radio-button": {"a": "radio button", "b": "1F518", "j": ["circle", "input", "geometric", "old", "button", "music", "radio"]}, "white-square-button": {"a": "white square button", "b": "1F533", "j": ["input", "outlined", "geometric", "shape", "square", "button"]}, "black-square-button": {"a": "black square button", "b": "1F532", "j": ["input", "geometric", "shape", "square", "button", "frame"]}, "chequered-flag": {"a": "chequered flag", "b": "1F3C1", "j": ["checkered", "contest", "finishline", "race", "racing", "gokart", "chequered"]}, "triangular-flag": {"a": "triangular flag", "b": "1F6A9", "j": ["place", "post", "milestone", "mark"]}, "crossed-flags": {"a": "crossed flags", "b": "1F38C", "j": ["cross", "Japanese", "crossed", "japanese", "celebration", "nation", "country", "border"]}, "black-flag": {"a": "black flag", "b": "1F3F4", "j": ["waving", "pirate"]}, "white-flag": {"a": "white flag", "b": "1F3F3", "j": ["waving", "give up", "loser", "losing", "fail", "lost", "surrender"]}, "rainbow-flag": {"a": "rainbow flag", "b": "1F3F3-FE0F-200D-1F308", "j": ["homosexual", "glbt", "flag", "pride", "bisexual", "gay", "queer", "lesbian", "lgbt", "rainbow", "transgender"]}, "transgender-flag": {"a": "transgender flag", "b": "1F3F3-FE0F-200D-26A7-FE0F", "j": ["flag", "lgbtq", "light blue", "pink", "white", "transgender"]}, "pirate-flag": {"a": "pirate flag", "b": "1F3F4-200D-2620-FE0F", "j": ["banner", "pirate", "skull", "flag", "Jolly Roger", "treasure", "plunder", "crossbones"]}, "flag-ascension-island": {"a": "flag: Ascension Island", "b": "1F1E6-1F1E8", "j": ["flag"]}, "flag-andorra": {"a": "flag: Andorra", "b": "1F1E6-1F1E9", "j": ["banner", "ad", "flag", "country", "nation"]}, "flag-united-arab-emirates": {"a": "flag: United Arab Emirates", "b": "1F1E6-1F1EA", "j": ["united", "banner", "flag", "arab", "nation", "country", "emirates"]}, "flag-afghanistan": {"a": "flag: Afghanistan", "b": "1F1E6-1F1EB", "j": ["banner", "af", "flag", "country", "nation"]}, "flag-antigua--barbuda": {"a": "flag: Antigua & Barbuda", "b": "1F1E6-1F1EC", "j": ["banner", "antigua", "flag", "flag_antigua_barbuda", "nation", "country", "barbuda"]}, "flag-anguilla": {"a": "flag: Anguilla", "b": "1F1E6-1F1EE", "j": ["banner", "flag", "ai", "country", "nation"]}, "flag-albania": {"a": "flag: Albania", "b": "1F1E6-1F1F1", "j": ["banner", "flag", "nation", "country", "al"]}, "flag-armenia": {"a": "flag: Armenia", "b": "1F1E6-1F1F2", "j": ["banner", "am", "flag", "nation", "country"]}, "flag-angola": {"a": "flag: Angola", "b": "1F1E6-1F1F4", "j": ["banner", "flag", "ao", "country", "nation"]}, "flag-antarctica": {"a": "flag: Antarctica", "b": "1F1E6-1F1F6", "j": ["banner", "flag", "nation", "country", "aq"]}, "flag-argentina": {"a": "flag: Argentina", "b": "1F1E6-1F1F7", "j": ["banner", "flag", "ar", "nation", "country"]}, "flag-american-samoa": {"a": "flag: American Samoa", "b": "1F1E6-1F1F8", "j": ["banner", "flag", "ws", "nation", "country", "american"]}, "flag-austria": {"a": "flag: Austria", "b": "1F1E6-1F1F9", "j": ["banner", "at", "flag", "nation", "country"]}, "flag-australia": {"a": "flag: Australia", "b": "1F1E6-1F1FA", "j": ["banner", "flag", "au", "nation", "country"]}, "flag-aruba": {"a": "flag: Aruba", "b": "1F1E6-1F1FC", "j": ["banner", "flag", "nation", "country", "aw"]}, "flag-land-islands": {"a": "flag: Åland Islands", "b": "1F1E6-1F1FD", "j": ["banner", "Åland", "flag_aland_islands", "flag", "islands", "nation", "country"]}, "flag-azerbaijan": {"a": "flag: Azerbaijan", "b": "1F1E6-1F1FF", "j": ["banner", "az", "flag", "country", "nation"]}, "flag-bosnia--herzegovina": {"a": "flag: Bosnia & Herzegovina", "b": "1F1E7-1F1E6", "j": ["banner", "flag", "flag_bosnia_herzegovina", "herzegovina", "bosnia", "nation", "country"]}, "flag-barbados": {"a": "flag: Barbados", "b": "1F1E7-1F1E7", "j": ["banner", "bb", "flag", "country", "nation"]}, "flag-bangladesh": {"a": "flag: Bangladesh", "b": "1F1E7-1F1E9", "j": ["banner", "flag", "country", "nation", "bd"]}, "flag-belgium": {"a": "flag: Belgium", "b": "1F1E7-1F1EA", "j": ["banner", "flag", "country", "nation", "be"]}, "flag-burkina-faso": {"a": "flag: Burkina Faso", "b": "1F1E7-1F1EB", "j": ["banner", "flag", "faso", "nation", "country", "burkina"]}, "flag-bulgaria": {"a": "flag: Bulgaria", "b": "1F1E7-1F1EC", "j": ["banner", "flag", "bg", "country", "nation"]}, "flag-bahrain": {"a": "flag: Bahrain", "b": "1F1E7-1F1ED", "j": ["banner", "flag", "bh", "country", "nation"]}, "flag-burundi": {"a": "flag: Burundi", "b": "1F1E7-1F1EE", "j": ["banner", "flag", "bi", "country", "nation"]}, "flag-benin": {"a": "flag: Benin", "b": "1F1E7-1F1EF", "j": ["banner", "flag", "bj", "country", "nation"]}, "flag-st-barthlemy": {"a": "flag: St. Barthélemy", "b": "1F1E7-1F1F1", "j": ["banner", "flag", "flag_st_barthelemy", "nation", "country", "barthélemy", "saint"]}, "flag-bermuda": {"a": "flag: Bermuda", "b": "1F1E7-1F1F2", "j": ["banner", "bm", "flag", "country", "nation"]}, "flag-brunei": {"a": "flag: Brunei", "b": "1F1E7-1F1F3", "j": ["banner", "darussalam", "flag", "nation", "bn", "country"]}, "flag-bolivia": {"a": "flag: Bolivia", "b": "1F1E7-1F1F4", "j": ["banner", "flag", "country", "nation", "bo"]}, "flag-caribbean-netherlands": {"a": "flag: Caribbean Netherlands", "b": "1F1E7-1F1F6", "j": ["banner", "flag", "bonaire", "nation", "country"]}, "flag-brazil": {"a": "flag: Brazil", "b": "1F1E7-1F1F7", "j": ["banner", "flag", "br", "country", "nation"]}, "flag-bahamas": {"a": "flag: Bahamas", "b": "1F1E7-1F1F8", "j": ["banner", "flag", "country", "nation", "bs"]}, "flag-bhutan": {"a": "flag: Bhutan", "b": "1F1E7-1F1F9", "j": ["bt", "banner", "flag", "country", "nation"]}, "flag-bouvet-island": {"a": "flag: Bouvet Island", "b": "1F1E7-1F1FB", "j": ["norway", "flag"]}, "flag-botswana": {"a": "flag: Botswana", "b": "1F1E7-1F1FC", "j": ["banner", "flag", "bw", "country", "nation"]}, "flag-belarus": {"a": "flag: Belarus", "b": "1F1E7-1F1FE", "j": ["banner", "flag", "nation", "country", "by"]}, "flag-belize": {"a": "flag: Belize", "b": "1F1E7-1F1FF", "j": ["banner", "flag", "country", "bz", "nation"]}, "flag-canada": {"a": "flag: Canada", "b": "1F1E8-1F1E6", "j": ["banner", "flag", "ca", "country", "nation"]}, "flag-cocos-keeling-islands": {"a": "flag: Cocos (Keeling) Islands", "b": "1F1E8-1F1E8", "j": ["keeling", "banner", "flag", "cocos", "islands", "nation", "country", "flag_cocos_islands"]}, "flag-congo--kinshasa": {"a": "flag: Congo - Kinshasa", "b": "1F1E8-1F1E9", "j": ["banner", "flag_congo_kinshasa", "republic", "democratic", "flag", "congo", "nation", "country"]}, "flag-central-african-republic": {"a": "flag: Central African Republic", "b": "1F1E8-1F1EB", "j": ["banner", "republic", "flag", "nation", "african", "country", "central"]}, "flag-congo--brazzaville": {"a": "flag: Congo - Brazzaville", "b": "1F1E8-1F1EC", "j": ["banner", "flag", "congo", "flag_congo_brazzaville", "country", "nation"]}, "flag-switzerland": {"a": "flag: Switzerland", "b": "1F1E8-1F1ED", "j": ["banner", "ch", "flag", "country", "nation"]}, "flag-cte-divoire": {"a": "flag: Côte d’Ivoire", "b": "1F1E8-1F1EE", "j": ["banner", "coast", "flag", "country", "nation", "flag_cote_d_ivoire", "ivory"]}, "flag-cook-islands": {"a": "flag: Cook Islands", "b": "1F1E8-1F1F0", "j": ["banner", "flag", "islands", "nation", "country", "cook"]}, "flag-chile": {"a": "flag: Chile", "b": "1F1E8-1F1F1", "j": ["flag", "banner", "nation", "country"]}, "flag-cameroon": {"a": "flag: Cameroon", "b": "1F1E8-1F1F2", "j": ["banner", "flag", "cm", "nation", "country"]}, "flag-china": {"a": "flag: China", "b": "1F1E8-1F1F3", "j": ["china", "banner", "flag", "prc", "country", "chinese", "nation"]}, "flag-colombia": {"a": "flag: Colombia", "b": "1F1E8-1F1F4", "j": ["banner", "co", "flag", "nation", "country"]}, "flag-clipperton-island": {"a": "flag: Clipperton Island", "b": "1F1E8-1F1F5", "j": ["flag"]}, "flag-costa-rica": {"a": "flag: Costa Rica", "b": "1F1E8-1F1F7", "j": ["banner", "flag", "nation", "rica", "costa", "country"]}, "flag-cuba": {"a": "flag: Cuba", "b": "1F1E8-1F1FA", "j": ["banner", "cu", "flag", "nation", "country"]}, "flag-cape-verde": {"a": "flag: Cape Verde", "b": "1F1E8-1F1FB", "j": ["banner", "flag", "cabo", "verde", "nation", "country"]}, "flag-curaao": {"a": "flag: Curaçao", "b": "1F1E8-1F1FC", "j": ["banner", "flag", "curaçao", "country", "flag_curacao", "nation"]}, "flag-christmas-island": {"a": "flag: Christmas Island", "b": "1F1E8-1F1FD", "j": ["banner", "flag", "nation", "country", "island", "christmas"]}, "flag-cyprus": {"a": "flag: Cyprus", "b": "1F1E8-1F1FE", "j": ["banner", "cy", "flag", "country", "nation"]}, "flag-czechia": {"a": "flag: Czechia", "b": "1F1E8-1F1FF", "j": ["cz", "banner", "flag", "nation", "country"]}, "flag-germany": {"a": "flag: Germany", "b": "1F1E9-1F1EA", "j": ["banner", "flag", "german", "country", "nation"]}, "flag-diego-garcia": {"a": "flag: Diego Garcia", "b": "1F1E9-1F1EC", "j": ["flag"]}, "flag-djibouti": {"a": "flag: Djibouti", "b": "1F1E9-1F1EF", "j": ["banner", "flag", "dj", "country", "nation"]}, "flag-denmark": {"a": "flag: Denmark", "b": "1F1E9-1F1F0", "j": ["banner", "flag", "dk", "country", "nation"]}, "flag-dominica": {"a": "flag: Dominica", "b": "1F1E9-1F1F2", "j": ["banner", "dm", "flag", "country", "nation"]}, "flag-dominican-republic": {"a": "flag: Dominican Republic", "b": "1F1E9-1F1F4", "j": ["banner", "republic", "flag", "dominican", "nation", "country"]}, "flag-algeria": {"a": "flag: Algeria", "b": "1F1E9-1F1FF", "j": ["banner", "flag", "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": ["banner", "flag", "ec", "country", "nation"]}, "flag-estonia": {"a": "flag: Estonia", "b": "1F1EA-1F1EA", "j": ["ee", "banner", "flag", "nation", "country"]}, "flag-egypt": {"a": "flag: Egypt", "b": "1F1EA-1F1EC", "j": ["banner", "flag", "eg", "country", "nation"]}, "flag-western-sahara": {"a": "flag: Western Sahara", "b": "1F1EA-1F1ED", "j": ["banner", "flag", "sahara", "nation", "country", "western"]}, "flag-eritrea": {"a": "flag: Eritrea", "b": "1F1EA-1F1F7", "j": ["banner", "flag", "er", "country", "nation"]}, "flag-spain": {"a": "flag: Spain", "b": "1F1EA-1F1F8", "j": ["spain", "banner", "flag", "country", "nation"]}, "flag-ethiopia": {"a": "flag: Ethiopia", "b": "1F1EA-1F1F9", "j": ["banner", "flag", "et", "nation", "country"]}, "flag-european-union": {"a": "flag: European Union", "b": "1F1EA-1F1FA", "j": ["banner", "european", "flag", "union"]}, "flag-finland": {"a": "flag: Finland", "b": "1F1EB-1F1EE", "j": ["banner", "fi", "flag", "nation", "country"]}, "flag-fiji": {"a": "flag: Fiji", "b": "1F1EB-1F1EF", "j": ["banner", "fj", "flag", "country", "nation"]}, "flag-falkland-islands": {"a": "flag: Falkland Islands", "b": "1F1EB-1F1F0", "j": ["banner", "flag", "malvinas", "falkland", "islands", "nation", "country"]}, "flag-micronesia": {"a": "flag: Micronesia", "b": "1F1EB-1F1F2", "j": ["banner", "flag", "federated", "micronesia", "nation", "country", "states"]}, "flag-faroe-islands": {"a": "flag: Faroe Islands", "b": "1F1EB-1F1F4", "j": ["banner", "flag", "islands", "nation", "country", "faroe"]}, "flag-france": {"a": "flag: France", "b": "1F1EB-1F1F7", "j": ["banner", "flag", "french", "france", "nation", "country"]}, "flag-gabon": {"a": "flag: Gabon", "b": "1F1EC-1F1E6", "j": ["banner", "flag", "ga", "country", "nation"]}, "flag-united-kingdom": {"a": "flag: United Kingdom", "b": "1F1EC-1F1E7", "j": ["united", "banner", "UK", "england", "ireland", "flag", "british", "britain", "english", "northern", "great", "kingdom", "nation", "country", "union jack"]}, "flag-grenada": {"a": "flag: Grenada", "b": "1F1EC-1F1E9", "j": ["banner", "flag", "gd", "country", "nation"]}, "flag-georgia": {"a": "flag: Georgia", "b": "1F1EC-1F1EA", "j": ["banner", "flag", "ge", "country", "nation"]}, "flag-french-guiana": {"a": "flag: French Guiana", "b": "1F1EC-1F1EB", "j": ["banner", "flag", "french", "guiana", "nation", "country"]}, "flag-guernsey": {"a": "flag: Guernsey", "b": "1F1EC-1F1EC", "j": ["banner", "gg", "flag", "country", "nation"]}, "flag-ghana": {"a": "flag: Ghana", "b": "1F1EC-1F1ED", "j": ["banner", "flag", "country", "nation", "gh"]}, "flag-gibraltar": {"a": "flag: Gibraltar", "b": "1F1EC-1F1EE", "j": ["banner", "flag", "nation", "country", "gi"]}, "flag-greenland": {"a": "flag: Greenland", "b": "1F1EC-1F1F1", "j": ["banner", "flag", "country", "nation", "gl"]}, "flag-gambia": {"a": "flag: Gambia", "b": "1F1EC-1F1F2", "j": ["banner", "gm", "flag", "country", "nation"]}, "flag-guinea": {"a": "flag: Guinea", "b": "1F1EC-1F1F3", "j": ["banner", "flag", "gn", "country", "nation"]}, "flag-guadeloupe": {"a": "flag: Guadeloupe", "b": "1F1EC-1F1F5", "j": ["banner", "flag", "country", "gp", "nation"]}, "flag-equatorial-guinea": {"a": "flag: Equatorial Guinea", "b": "1F1EC-1F1F6", "j": ["banner", "flag", "gn", "equatorial", "nation", "country"]}, "flag-greece": {"a": "flag: Greece", "b": "1F1EC-1F1F7", "j": ["banner", "flag", "gr", "nation", "country"]}, "flag-south-georgia--south-sandwich-islands": {"a": "flag: South Georgia & South Sandwich Islands", "b": "1F1EC-1F1F8", "j": ["banner", "flag", "flag_south_georgia_south_sandwich_islands", "islands", "georgia", "nation", "country", "sandwich", "south"]}, "flag-guatemala": {"a": "flag: Guatemala", "b": "1F1EC-1F1F9", "j": ["banner", "flag", "gt", "country", "nation"]}, "flag-guam": {"a": "flag: Guam", "b": "1F1EC-1F1FA", "j": ["banner", "flag", "nation", "country", "gu"]}, "flag-guineabissau": {"a": "flag: Guinea-Bissau", "b": "1F1EC-1F1FC", "j": ["banner", "flag_guinea_bissau", "flag", "nation", "country", "bissau", "gw"]}, "flag-guyana": {"a": "flag: Guyana", "b": "1F1EC-1F1FE", "j": ["banner", "flag", "gy", "country", "nation"]}, "flag-hong-kong-sar-china": {"a": "flag: Hong Kong SAR China", "b": "1F1ED-1F1F0", "j": ["banner", "kong", "flag", "hong", "nation", "country"]}, "flag-heard--mcdonald-islands": {"a": "flag: Heard & McDonald Islands", "b": "1F1ED-1F1F2", "j": ["flag_heard_mcdonald_islands", "flag"]}, "flag-honduras": {"a": "flag: Honduras", "b": "1F1ED-1F1F3", "j": ["banner", "flag", "hn", "nation", "country"]}, "flag-croatia": {"a": "flag: Croatia", "b": "1F1ED-1F1F7", "j": ["banner", "flag", "country", "nation", "hr"]}, "flag-haiti": {"a": "flag: Haiti", "b": "1F1ED-1F1F9", "j": ["banner", "flag", "country", "nation", "ht"]}, "flag-hungary": {"a": "flag: Hungary", "b": "1F1ED-1F1FA", "j": ["banner", "flag", "hu", "country", "nation"]}, "flag-canary-islands": {"a": "flag: Canary Islands", "b": "1F1EE-1F1E8", "j": ["banner", "flag", "canary", "islands", "nation", "country"]}, "flag-indonesia": {"a": "flag: Indonesia", "b": "1F1EE-1F1E9", "j": ["flag", "banner", "nation", "country"]}, "flag-ireland": {"a": "flag: Ireland", "b": "1F1EE-1F1EA", "j": ["banner", "flag", "ie", "nation", "country"]}, "flag-israel": {"a": "flag: Israel", "b": "1F1EE-1F1F1", "j": ["banner", "flag", "il", "country", "nation"]}, "flag-isle-of-man": {"a": "flag: Isle of Man", "b": "1F1EE-1F1F2", "j": ["isle", "banner", "man", "flag", "nation", "country"]}, "flag-india": {"a": "flag: India", "b": "1F1EE-1F1F3", "j": ["banner", "flag", "in", "country", "nation"]}, "flag-british-indian-ocean-territory": {"a": "flag: British Indian Ocean Territory", "b": "1F1EE-1F1F4", "j": ["banner", "flag", "british", "ocean", "nation", "territory", "country", "indian"]}, "flag-iraq": {"a": "flag: Iraq", "b": "1F1EE-1F1F6", "j": ["banner", "iq", "flag", "nation", "country"]}, "flag-iran": {"a": "flag: Iran", "b": "1F1EE-1F1F7", "j": ["banner", "republic", "flag", "iran", "islamic", "nation", "country"]}, "flag-iceland": {"a": "flag: Iceland", "b": "1F1EE-1F1F8", "j": ["banner", "flag", "is", "country", "nation"]}, "flag-italy": {"a": "flag: Italy", "b": "1F1EE-1F1F9", "j": ["banner", "flag", "italy", "country", "nation"]}, "flag-jersey": {"a": "flag: Jersey", "b": "1F1EF-1F1EA", "j": ["je", "banner", "flag", "nation", "country"]}, "flag-jamaica": {"a": "flag: Jamaica", "b": "1F1EF-1F1F2", "j": ["banner", "flag", "nation", "country", "jm"]}, "flag-jordan": {"a": "flag: Jordan", "b": "1F1EF-1F1F4", "j": ["jo", "banner", "flag", "nation", "country"]}, "flag-japan": {"a": "flag: Japan", "b": "1F1EF-1F1F5", "j": ["banner", "flag", "japanese", "nation", "country"]}, "flag-kenya": {"a": "flag: Kenya", "b": "1F1F0-1F1EA", "j": ["banner", "flag", "country", "nation", "ke"]}, "flag-kyrgyzstan": {"a": "flag: Kyrgyzstan", "b": "1F1F0-1F1EC", "j": ["kg", "banner", "flag", "nation", "country"]}, "flag-cambodia": {"a": "flag: Cambodia", "b": "1F1F0-1F1ED", "j": ["banner", "flag", "kh", "nation", "country"]}, "flag-kiribati": {"a": "flag: Kiribati", "b": "1F1F0-1F1EE", "j": ["banner", "flag", "nation", "country", "ki"]}, "flag-comoros": {"a": "flag: Comoros", "b": "1F1F0-1F1F2", "j": ["banner", "flag", "km", "country", "nation"]}, "flag-st-kitts--nevis": {"a": "flag: St. Kitts & Nevis", "b": "1F1F0-1F1F3", "j": ["nevis", "banner", "kitts", "flag", "flag_st_kitts_nevis", "nation", "country", "saint"]}, "flag-north-korea": {"a": "flag: North Korea", "b": "1F1F0-1F1F5", "j": ["banner", "flag", "korea", "country", "nation", "north"]}, "flag-south-korea": {"a": "flag: South Korea", "b": "1F1F0-1F1F7", "j": ["banner", "flag", "korea", "nation", "country", "south"]}, "flag-kuwait": {"a": "flag: Kuwait", "b": "1F1F0-1F1FC", "j": ["banner", "flag", "kw", "nation", "country"]}, "flag-cayman-islands": {"a": "flag: Cayman Islands", "b": "1F1F0-1F1FE", "j": ["banner", "flag", "cayman", "islands", "nation", "country"]}, "flag-kazakhstan": {"a": "flag: Kazakhstan", "b": "1F1F0-1F1FF", "j": ["kz", "banner", "flag", "nation", "country"]}, "flag-laos": {"a": "flag: Laos", "b": "1F1F1-1F1E6", "j": ["banner", "republic", "flag", "democratic", "nation", "country", "lao"]}, "flag-lebanon": {"a": "flag: Lebanon", "b": "1F1F1-1F1E7", "j": ["banner", "flag", "lb", "country", "nation"]}, "flag-st-lucia": {"a": "flag: St. Lucia", "b": "1F1F1-1F1E8", "j": ["banner", "flag", "lucia", "nation", "country", "saint"]}, "flag-liechtenstein": {"a": "flag: Liechtenstein", "b": "1F1F1-1F1EE", "j": ["banner", "flag", "country", "nation", "li"]}, "flag-sri-lanka": {"a": "flag: Sri Lanka", "b": "1F1F1-1F1F0", "j": ["lanka", "banner", "flag", "nation", "country", "sri"]}, "flag-liberia": {"a": "flag: Liberia", "b": "1F1F1-1F1F7", "j": ["banner", "flag", "lr", "country", "nation"]}, "flag-lesotho": {"a": "flag: Lesotho", "b": "1F1F1-1F1F8", "j": ["banner", "flag", "country", "nation", "ls"]}, "flag-lithuania": {"a": "flag: Lithuania", "b": "1F1F1-1F1F9", "j": ["banner", "flag", "lt", "country", "nation"]}, "flag-luxembourg": {"a": "flag: Luxembourg", "b": "1F1F1-1F1FA", "j": ["banner", "flag", "country", "nation", "lu"]}, "flag-latvia": {"a": "flag: Latvia", "b": "1F1F1-1F1FB", "j": ["lv", "banner", "flag", "nation", "country"]}, "flag-libya": {"a": "flag: Libya", "b": "1F1F1-1F1FE", "j": ["banner", "ly", "flag", "country", "nation"]}, "flag-morocco": {"a": "flag: Morocco", "b": "1F1F2-1F1E6", "j": ["banner", "ma", "flag", "country", "nation"]}, "flag-monaco": {"a": "flag: Monaco", "b": "1F1F2-1F1E8", "j": ["banner", "flag", "mc", "nation", "country"]}, "flag-moldova": {"a": "flag: Moldova", "b": "1F1F2-1F1E9", "j": ["banner", "republic", "flag", "country", "nation", "moldova"]}, "flag-montenegro": {"a": "flag: Montenegro", "b": "1F1F2-1F1EA", "j": ["banner", "flag", "me", "country", "nation"]}, "flag-st-martin": {"a": "flag: St. Martin", "b": "1F1F2-1F1EB", "j": ["flag"]}, "flag-madagascar": {"a": "flag: Madagascar", "b": "1F1F2-1F1EC", "j": ["banner", "flag", "country", "nation", "mg"]}, "flag-marshall-islands": {"a": "flag: Marshall Islands", "b": "1F1F2-1F1ED", "j": ["banner", "flag", "islands", "nation", "country", "marshall"]}, "flag-north-macedonia": {"a": "flag: North Macedonia", "b": "1F1F2-1F1F0", "j": ["banner", "flag", "macedonia", "country", "nation"]}, "flag-mali": {"a": "flag: Mali", "b": "1F1F2-1F1F1", "j": ["banner", "flag", "country", "ml", "nation"]}, "flag-myanmar-burma": {"a": "flag: Myanmar (Burma)", "b": "1F1F2-1F1F2", "j": ["banner", "flag", "flag_myanmar", "mm", "country", "nation"]}, "flag-mongolia": {"a": "flag: Mongolia", "b": "1F1F2-1F1F3", "j": ["banner", "flag", "mn", "country", "nation"]}, "flag-macao-sar-china": {"a": "flag: Macao SAR China", "b": "1F1F2-1F1F4", "j": ["banner", "flag", "country", "macao", "nation"]}, "flag-northern-mariana-islands": {"a": "flag: Northern Mariana Islands", "b": "1F1F2-1F1F5", "j": ["banner", "flag", "northern", "islands", "nation", "country", "mariana"]}, "flag-martinique": {"a": "flag: Martinique", "b": "1F1F2-1F1F6", "j": ["banner", "flag", "country", "nation", "mq"]}, "flag-mauritania": {"a": "flag: Mauritania", "b": "1F1F2-1F1F7", "j": ["banner", "mr", "flag", "country", "nation"]}, "flag-montserrat": {"a": "flag: Montserrat", "b": "1F1F2-1F1F8", "j": ["banner", "flag", "ms", "country", "nation"]}, "flag-malta": {"a": "flag: Malta", "b": "1F1F2-1F1F9", "j": ["banner", "mt", "flag", "country", "nation"]}, "flag-mauritius": {"a": "flag: Mauritius", "b": "1F1F2-1F1FA", "j": ["banner", "flag", "country", "nation", "mu"]}, "flag-maldives": {"a": "flag: Maldives", "b": "1F1F2-1F1FB", "j": ["banner", "mv", "flag", "country", "nation"]}, "flag-malawi": {"a": "flag: Malawi", "b": "1F1F2-1F1FC", "j": ["banner", "flag", "country", "nation", "mw"]}, "flag-mexico": {"a": "flag: Mexico", "b": "1F1F2-1F1FD", "j": ["banner", "flag", "mx", "nation", "country"]}, "flag-malaysia": {"a": "flag: Malaysia", "b": "1F1F2-1F1FE", "j": ["banner", "flag", "my", "country", "nation"]}, "flag-mozambique": {"a": "flag: Mozambique", "b": "1F1F2-1F1FF", "j": ["banner", "flag", "country", "nation", "mz"]}, "flag-namibia": {"a": "flag: Namibia", "b": "1F1F3-1F1E6", "j": ["banner", "flag", "nation", "country", "na"]}, "flag-new-caledonia": {"a": "flag: New Caledonia", "b": "1F1F3-1F1E8", "j": ["banner", "flag", "new", "nation", "country", "caledonia"]}, "flag-niger": {"a": "flag: Niger", "b": "1F1F3-1F1EA", "j": ["banner", "flag", "ne", "nation", "country"]}, "flag-norfolk-island": {"a": "flag: Norfolk Island", "b": "1F1F3-1F1EB", "j": ["banner", "flag", "nation", "country", "norfolk", "island"]}, "flag-nigeria": {"a": "flag: Nigeria", "b": "1F1F3-1F1EC", "j": ["flag", "banner", "nation", "country"]}, "flag-nicaragua": {"a": "flag: Nicaragua", "b": "1F1F3-1F1EE", "j": ["banner", "flag", "ni", "country", "nation"]}, "flag-netherlands": {"a": "flag: Netherlands", "b": "1F1F3-1F1F1", "j": ["banner", "flag", "nl", "country", "nation"]}, "flag-norway": {"a": "flag: Norway", "b": "1F1F3-1F1F4", "j": ["banner", "flag", "no", "country", "nation"]}, "flag-nepal": {"a": "flag: Nepal", "b": "1F1F3-1F1F5", "j": ["banner", "flag", "country", "nation", "np"]}, "flag-nauru": {"a": "flag: Nauru", "b": "1F1F3-1F1F7", "j": ["banner", "nr", "flag", "nation", "country"]}, "flag-niue": {"a": "flag: Niue", "b": "1F1F3-1F1FA", "j": ["banner", "flag", "nu", "country", "nation"]}, "flag-new-zealand": {"a": "flag: New Zealand", "b": "1F1F3-1F1FF", "j": ["banner", "flag", "zealand", "nation", "country", "new"]}, "flag-oman": {"a": "flag: Oman", "b": "1F1F4-1F1F2", "j": ["banner", "flag", "nation", "country", "om_symbol"]}, "flag-panama": {"a": "flag: Panama", "b": "1F1F5-1F1E6", "j": ["banner", "flag", "pa", "country", "nation"]}, "flag-peru": {"a": "flag: Peru", "b": "1F1F5-1F1EA", "j": ["banner", "flag", "country", "nation", "pe"]}, "flag-french-polynesia": {"a": "flag: French Polynesia", "b": "1F1F5-1F1EB", "j": ["banner", "flag", "french", "polynesia", "nation", "country"]}, "flag-papua-new-guinea": {"a": "flag: Papua New Guinea", "b": "1F1F5-1F1EC", "j": ["banner", "flag", "papua", "nation", "country", "new", "guinea"]}, "flag-philippines": {"a": "flag: Philippines", "b": "1F1F5-1F1ED", "j": ["banner", "flag", "ph", "country", "nation"]}, "flag-pakistan": {"a": "flag: Pakistan", "b": "1F1F5-1F1F0", "j": ["banner", "flag", "pk", "country", "nation"]}, "flag-poland": {"a": "flag: Poland", "b": "1F1F5-1F1F1", "j": ["banner", "pl", "flag", "country", "nation"]}, "flag-st-pierre--miquelon": {"a": "flag: St. Pierre & Miquelon", "b": "1F1F5-1F1F2", "j": ["banner", "flag", "flag_st_pierre_miquelon", "miquelon", "nation", "country", "saint", "pierre"]}, "flag-pitcairn-islands": {"a": "flag: Pitcairn Islands", "b": "1F1F5-1F1F3", "j": ["banner", "flag", "nation", "country", "pitcairn"]}, "flag-puerto-rico": {"a": "flag: Puerto Rico", "b": "1F1F5-1F1F7", "j": ["banner", "puerto", "flag", "nation", "country", "rico"]}, "flag-palestinian-territories": {"a": "flag: Palestinian Territories", "b": "1F1F5-1F1F8", "j": ["banner", "flag", "palestinian", "territories", "nation", "country", "palestine"]}, "flag-portugal": {"a": "flag: Portugal", "b": "1F1F5-1F1F9", "j": ["banner", "flag", "country", "nation", "pt"]}, "flag-palau": {"a": "flag: Palau", "b": "1F1F5-1F1FC", "j": ["banner", "flag", "country", "nation", "pw"]}, "flag-paraguay": {"a": "flag: Paraguay", "b": "1F1F5-1F1FE", "j": ["banner", "flag", "py", "nation", "country"]}, "flag-qatar": {"a": "flag: Qatar", "b": "1F1F6-1F1E6", "j": ["banner", "flag", "country", "nation", "qa"]}, "flag-runion": {"a": "flag: Réunion", "b": "1F1F7-1F1EA", "j": ["banner", "flag", "réunion", "country", "nation", "flag_reunion"]}, "flag-romania": {"a": "flag: Romania", "b": "1F1F7-1F1F4", "j": ["banner", "flag", "nation", "country", "ro"]}, "flag-serbia": {"a": "flag: Serbia", "b": "1F1F7-1F1F8", "j": ["banner", "flag", "rs", "country", "nation"]}, "flag-russia": {"a": "flag: Russia", "b": "1F1F7-1F1FA", "j": ["banner", "russian", "flag", "federation", "nation", "country"]}, "flag-rwanda": {"a": "flag: Rwanda", "b": "1F1F7-1F1FC", "j": ["banner", "flag", "country", "nation", "rw"]}, "flag-saudi-arabia": {"a": "flag: Saudi Arabia", "b": "1F1F8-1F1E6", "j": ["flag", "banner", "nation", "country"]}, "flag-solomon-islands": {"a": "flag: Solomon Islands", "b": "1F1F8-1F1E7", "j": ["banner", "flag", "islands", "nation", "country", "solomon"]}, "flag-seychelles": {"a": "flag: Seychelles", "b": "1F1F8-1F1E8", "j": ["banner", "flag", "sc", "nation", "country"]}, "flag-sudan": {"a": "flag: Sudan", "b": "1F1F8-1F1E9", "j": ["banner", "sd", "flag", "country", "nation"]}, "flag-sweden": {"a": "flag: Sweden", "b": "1F1F8-1F1EA", "j": ["banner", "flag", "se", "country", "nation"]}, "flag-singapore": {"a": "flag: Singapore", "b": "1F1F8-1F1EC", "j": ["banner", "flag", "country", "nation", "sg"]}, "flag-st-helena": {"a": "flag: St. Helena", "b": "1F1F8-1F1ED", "j": ["banner", "flag", "cunha", "ascension", "helena", "nation", "country", "saint", "tristan"]}, "flag-slovenia": {"a": "flag: Slovenia", "b": "1F1F8-1F1EE", "j": ["banner", "flag", "nation", "si", "country"]}, "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": ["sk", "banner", "flag", "nation", "country"]}, "flag-sierra-leone": {"a": "flag: Sierra Leone", "b": "1F1F8-1F1F1", "j": ["banner", "flag", "sierra", "leone", "nation", "country"]}, "flag-san-marino": {"a": "flag: San Marino", "b": "1F1F8-1F1F2", "j": ["banner", "flag", "san", "nation", "country", "marino"]}, "flag-senegal": {"a": "flag: Senegal", "b": "1F1F8-1F1F3", "j": ["banner", "flag", "nation", "sn", "country"]}, "flag-somalia": {"a": "flag: Somalia", "b": "1F1F8-1F1F4", "j": ["banner", "so", "flag", "country", "nation"]}, "flag-suriname": {"a": "flag: Suriname", "b": "1F1F8-1F1F7", "j": ["banner", "flag", "sr", "country", "nation"]}, "flag-south-sudan": {"a": "flag: South Sudan", "b": "1F1F8-1F1F8", "j": ["banner", "sd", "flag", "nation", "country", "south"]}, "flag-so-tom--prncipe": {"a": "flag: São Tomé & Príncipe", "b": "1F1F8-1F1F9", "j": ["tome", "sao", "banner", "flag", "principe", "nation", "flag_sao_tome_principe", "country"]}, "flag-el-salvador": {"a": "flag: El Salvador", "b": "1F1F8-1F1FB", "j": ["banner", "flag", "nation", "country", "salvador", "el"]}, "flag-sint-maarten": {"a": "flag: Sint Maarten", "b": "1F1F8-1F1FD", "j": ["banner", "flag", "maarten", "dutch", "sint", "nation", "country"]}, "flag-syria": {"a": "flag: Syria", "b": "1F1F8-1F1FE", "j": ["banner", "republic", "flag", "syrian", "arab", "nation", "country"]}, "flag-eswatini": {"a": "flag: Eswatini", "b": "1F1F8-1F1FF", "j": ["banner", "flag", "sz", "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": ["banner", "caicos", "flag", "islands", "nation", "country", "turks", "flag_turks_caicos_islands"]}, "flag-chad": {"a": "flag: Chad", "b": "1F1F9-1F1E9", "j": ["banner", "td", "flag", "country", "nation"]}, "flag-french-southern-territories": {"a": "flag: French Southern Territories", "b": "1F1F9-1F1EB", "j": ["banner", "flag", "french", "southern", "territories", "nation", "country"]}, "flag-togo": {"a": "flag: Togo", "b": "1F1F9-1F1EC", "j": ["banner", "flag", "tg", "country", "nation"]}, "flag-thailand": {"a": "flag: Thailand", "b": "1F1F9-1F1ED", "j": ["banner", "flag", "country", "nation", "th"]}, "flag-tajikistan": {"a": "flag: Tajikistan", "b": "1F1F9-1F1EF", "j": ["banner", "flag", "tj", "country", "nation"]}, "flag-tokelau": {"a": "flag: Tokelau", "b": "1F1F9-1F1F0", "j": ["tk", "banner", "flag", "country", "nation"]}, "flag-timorleste": {"a": "flag: Timor-Leste", "b": "1F1F9-1F1F1", "j": ["leste", "banner", "timor", "flag", "nation", "country", "flag_timor_leste"]}, "flag-turkmenistan": {"a": "flag: Turkmenistan", "b": "1F1F9-1F1F2", "j": ["flag", "banner", "country", "nation"]}, "flag-tunisia": {"a": "flag: Tunisia", "b": "1F1F9-1F1F3", "j": ["banner", "flag", "country", "nation", "tn"]}, "flag-tonga": {"a": "flag: Tonga", "b": "1F1F9-1F1F4", "j": ["banner", "flag", "country", "nation", "to"]}, "flag-turkey": {"a": "flag: Turkey", "b": "1F1F9-1F1F7", "j": ["banner", "flag", "nation", "turkey", "country"]}, "flag-trinidad--tobago": {"a": "flag: Trinidad & Tobago", "b": "1F1F9-1F1F9", "j": ["banner", "flag_trinidad_tobago", "flag", "trinidad", "nation", "country", "tobago"]}, "flag-tuvalu": {"a": "flag: Tuvalu", "b": "1F1F9-1F1FB", "j": ["flag", "banner", "nation", "country"]}, "flag-taiwan": {"a": "flag: Taiwan", "b": "1F1F9-1F1FC", "j": ["banner", "flag", "country", "nation", "tw"]}, "flag-tanzania": {"a": "flag: Tanzania", "b": "1F1F9-1F1FF", "j": ["united", "banner", "republic", "flag", "nation", "country", "tanzania"]}, "flag-ukraine": {"a": "flag: Ukraine", "b": "1F1FA-1F1E6", "j": ["banner", "flag", "ua", "country", "nation"]}, "flag-uganda": {"a": "flag: Uganda", "b": "1F1FA-1F1EC", "j": ["banner", "flag", "ug", "country", "nation"]}, "flag-us-outlying-islands": {"a": "flag: U.S. Outlying Islands", "b": "1F1FA-1F1F2", "j": ["flag_u_s_outlying_islands", "flag"]}, "flag-united-nations": {"a": "flag: United Nations", "b": "1F1FA-1F1F3", "j": ["banner", "un", "flag"]}, "flag-united-states": {"a": "flag: United States", "b": "1F1FA-1F1F8", "j": ["united", "banner", "flag", "america", "nation", "country", "states"]}, "flag-uruguay": {"a": "flag: Uruguay", "b": "1F1FA-1F1FE", "j": ["banner", "flag", "uy", "country", "nation"]}, "flag-uzbekistan": {"a": "flag: Uzbekistan", "b": "1F1FA-1F1FF", "j": ["banner", "uz", "flag", "country", "nation"]}, "flag-vatican-city": {"a": "flag: Vatican City", "b": "1F1FB-1F1E6", "j": ["banner", "flag", "vatican", "city", "nation", "country"]}, "flag-st-vincent--grenadines": {"a": "flag: St. Vincent & Grenadines", "b": "1F1FB-1F1E8", "j": ["banner", "flag", "flag_st_vincent_grenadines", "grenadines", "nation", "country", "vincent", "saint"]}, "flag-venezuela": {"a": "flag: Venezuela", "b": "1F1FB-1F1EA", "j": ["banner", "republic", "flag", "bolivarian", "ve", "nation", "country"]}, "flag-british-virgin-islands": {"a": "flag: British Virgin Islands", "b": "1F1FB-1F1EC", "j": ["banner", "virgin", "bvi", "flag", "british", "islands", "nation", "country"]}, "flag-us-virgin-islands": {"a": "flag: U.S. Virgin Islands", "b": "1F1FB-1F1EE", "j": ["banner", "virgin", "flag", "us", "islands", "nation", "country", "flag_u_s_virgin_islands"]}, "flag-vietnam": {"a": "flag: Vietnam", "b": "1F1FB-1F1F3", "j": ["banner", "flag", "nation", "nam", "country", "viet"]}, "flag-vanuatu": {"a": "flag: Vanuatu", "b": "1F1FB-1F1FA", "j": ["banner", "flag", "vu", "country", "nation"]}, "flag-wallis--futuna": {"a": "flag: Wallis & Futuna", "b": "1F1FC-1F1EB", "j": ["banner", "flag_wallis_futuna", "flag", "wallis", "nation", "country", "futuna"]}, "flag-samoa": {"a": "flag: Samoa", "b": "1F1FC-1F1F8", "j": ["banner", "flag", "ws", "nation", "country"]}, "flag-kosovo": {"a": "flag: Kosovo", "b": "1F1FD-1F1F0", "j": ["banner", "flag", "country", "nation", "xk"]}, "flag-yemen": {"a": "flag: Yemen", "b": "1F1FE-1F1EA", "j": ["banner", "flag", "nation", "ye", "country"]}, "flag-mayotte": {"a": "flag: Mayotte", "b": "1F1FE-1F1F9", "j": ["banner", "flag", "yt", "country", "nation"]}, "flag-south-africa": {"a": "flag: South Africa", "b": "1F1FF-1F1E6", "j": ["banner", "africa", "flag", "nation", "country", "south"]}, "flag-zambia": {"a": "flag: Zambia", "b": "1F1FF-1F1F2", "j": ["banner", "flag", "zm", "country", "nation"]}, "flag-zimbabwe": {"a": "flag: Zimbabwe", "b": "1F1FF-1F1FC", "j": ["banner", "flag", "zw", "country", "nation"]}, "flag-england": {"a": "flag: England", "b": "1F3F4-E0067-E0062-E0065-E006E-E0067-E007F", "j": ["english", "flag"]}, "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": ["welsh", "flag"]}}, "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":["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 From 40929b96406ea510da36291e450351b4ac992375 Mon Sep 17 00:00:00 2001 From: oogm Date: Fri, 26 Mar 2021 17:21:33 +0100 Subject: [PATCH 108/249] Update CHANGES.md for a release with 1.1.4 --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 03d9a45544..feff52b7b3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,7 @@ Improvements 🙌: - Improve message with Emoji only detection (#3017) - Picture preview when replying. Also add the image preview in the message detail bottomsheet (#2916) - Api interceptor to allow app developers peek responses (#2986) + - Update reactions to Unicode 13.1 (#2998) Bugfix 🐛: - Fix bad theme change for the MainActivity @@ -68,7 +69,6 @@ Improvements 🙌: - Sending is now queuing by room and not uniquely to the session - Improve Snackbar duration (#2929) - Improve sending message state (#2937) - - Update reactions to Unicode 13.1 (#2998) Bugfix 🐛: - Try to fix crash about UrlPreview (#2640) From 8f67511b22b14c3f347291528c7f4480f4b4edea Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sat, 27 Mar 2021 19:20:16 +0000 Subject: [PATCH 109/249] Convert VerificationService to suspend functions Signed-off-by: Dominic Fischer --- .../java/org/matrix/android/sdk/common/CryptoTestHelper.kt | 3 +-- .../android/sdk/internal/crypto/verification/SASTest.kt | 6 ++---- .../api/session/crypto/verification/VerificationService.kt | 4 +--- .../crypto/verification/DefaultVerificationService.kt | 4 +--- .../crypto/verification/VerificationBottomSheetViewModel.kt | 3 +-- 5 files changed, 6 insertions(+), 14 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt index eb7e4a9fbe..12f7928835 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt @@ -337,8 +337,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { requestID, roomId, bob.myUserId, - bob.sessionParams.credentials.deviceId!!, - null) + bob.sessionParams.credentials.deviceId!!) // we should reach SHOW SAS on both var alicePovTx: OutgoingSasVerificationTransaction? = null diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt index a81f503e77..4ea8cdc074 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt @@ -593,16 +593,14 @@ class SASTest : InstrumentedTest { requestID!!, cryptoTestData.roomId, bobSession.myUserId, - bobSession.sessionParams.deviceId!!, - null) + bobSession.sessionParams.deviceId!!) bobVerificationService.beginKeyVerificationInDMs( VerificationMethod.SAS, requestID!!, cryptoTestData.roomId, aliceSession.myUserId, - aliceSession.sessionParams.deviceId!!, - null) + aliceSession.sessionParams.deviceId!!) // we should reach SHOW SAS on both var alicePovTx: SasVerificationTransaction? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt index 2413786ea9..54a1e896ae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.api.session.crypto.verification -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.LocalEcho @@ -79,8 +78,7 @@ interface VerificationService { transactionId: String, roomId: String, otherUserId: String, - otherDeviceId: String, - callback: MatrixCallback?): String? + otherDeviceId: String): String /** * Returns false if the request is unknown diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt index a92f5c5bf1..d9da88770c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto.verification import android.os.Handler import android.os.Looper import dagger.Lazy -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME @@ -1293,8 +1292,7 @@ internal class DefaultVerificationService @Inject constructor( transactionId: String, roomId: String, otherUserId: String, - otherDeviceId: String, - callback: MatrixCallback?): String? { + otherDeviceId: String): String { if (method == VerificationMethod.SAS) { val tx = DefaultOutgoingSASDefaultVerificationTransaction( setDeviceVerificationAction, diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt index 04ac79d4a4..0e230c6727 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt @@ -305,8 +305,7 @@ class VerificationBottomSheetViewModel @AssistedInject constructor( transactionId = action.pendingRequestTransactionId, roomId = roomId, otherUserId = request.otherUserId, - otherDeviceId = otherDevice ?: "", - callback = null + otherDeviceId = otherDevice ?: "" ) } Unit From f8718e397c27a18165bfe81f00a87b4a30d1a610 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sun, 8 Nov 2020 13:06:50 +0000 Subject: [PATCH 110/249] Convert ReadService to suspend functions Signed-off-by: Dominic Fischer --- .../sdk/api/session/room/read/ReadService.kt | 7 +++-- .../session/room/read/DefaultReadService.kt | 26 +++++-------------- .../home/room/detail/RoomDetailViewModel.kt | 16 +++++++++--- .../NotificationBroadcastReceiver.kt | 10 +++++-- 4 files changed, 29 insertions(+), 30 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt index 4f44c9a912..b037a3f366 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.api.session.room.read import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.util.Optional @@ -35,17 +34,17 @@ interface ReadService { /** * Force the read marker to be set on the latest event. */ - fun markAsRead(params: MarkAsReadParams = MarkAsReadParams.BOTH, callback: MatrixCallback) + suspend fun markAsRead(params: MarkAsReadParams = MarkAsReadParams.BOTH) /** * Set the read receipt on the event with provided eventId. */ - fun setReadReceipt(eventId: String, callback: MatrixCallback) + suspend fun setReadReceipt(eventId: String) /** * Set the read marker on the event with provided eventId. */ - fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback) + suspend fun setReadMarker(fullyReadEventId: String) /** * Check if an event is already read, ie. your read receipt is set on a more recent event. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt index 3cf8cfe5ee..d4d03dca05 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt @@ -22,7 +22,6 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.read.ReadService import org.matrix.android.sdk.api.util.Optional @@ -36,7 +35,6 @@ 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.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith internal class DefaultReadService @AssistedInject constructor( @Assisted private val roomId: String, @@ -52,35 +50,23 @@ internal class DefaultReadService @AssistedInject constructor( fun create(roomId: String): DefaultReadService } - override fun markAsRead(params: ReadService.MarkAsReadParams, callback: MatrixCallback) { + override suspend fun markAsRead(params: ReadService.MarkAsReadParams) { val taskParams = SetReadMarkersTask.Params( roomId = roomId, forceReadMarker = params.forceReadMarker(), forceReadReceipt = params.forceReadReceipt() ) - setReadMarkersTask - .configureWith(taskParams) { - this.callback = callback - } - .executeBy(taskExecutor) + setReadMarkersTask.execute(taskParams) } - override fun setReadReceipt(eventId: String, callback: MatrixCallback) { + override suspend fun setReadReceipt(eventId: String) { val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = null, readReceiptEventId = eventId) - setReadMarkersTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + setReadMarkersTask.execute(params) } - override fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback) { + override suspend fun setReadMarker(fullyReadEventId: String) { val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = fullyReadEventId, readReceiptEventId = null) - setReadMarkersTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + setReadMarkersTask.execute(params) } override fun isEventRead(eventId: String): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index af3d5461ef..120e80a1b6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -181,7 +181,9 @@ class RoomDetailViewModel @AssistedInject constructor( observePowerLevel() updateShowDialerOptionState() room.getRoomSummaryLive() - room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT, NoOpMatrixCallback()) + viewModelScope.launch { + room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) + } // Inform the SDK that the room is displayed session.onRoomDisplayed(initialState.roomId) callManager.addPstnSupportListener(this) @@ -546,7 +548,9 @@ class RoomDetailViewModel @AssistedInject constructor( private fun stopTrackingUnreadMessages() { if (trackUnreadMessages.getAndSet(false)) { mostRecentDisplayedEvent?.root?.eventId?.also { - room.setReadMarker(it, callback = NoOpMatrixCallback()) + viewModelScope.launch { + room.setReadMarker(it) + } } mostRecentDisplayedEvent = null } @@ -1248,14 +1252,18 @@ class RoomDetailViewModel @AssistedInject constructor( } } bufferedMostRecentDisplayedEvent.root.eventId?.let { eventId -> - room.setReadReceipt(eventId, callback = NoOpMatrixCallback()) + viewModelScope.launch { + room.setReadReceipt(eventId) + } } }) .disposeOnClear() } private fun handleMarkAllAsRead() { - room.markAsRead(ReadService.MarkAsReadParams.BOTH, NoOpMatrixCallback()) + viewModelScope.launch { + room.markAsRead(ReadService.MarkAsReadParams.BOTH) + } } private fun handleReportContent(action: RoomDetailAction.ReportContent) { diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt index d79d16a052..c4d7376c55 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt @@ -23,6 +23,8 @@ import androidx.core.app.RemoteInput import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.vectorComponent +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.Room @@ -88,8 +90,12 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { private fun handleMarkAsRead(roomId: String) { activeSessionHolder.getActiveSession().let { session -> - session.getRoom(roomId) - ?.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT, NoOpMatrixCallback()) + val room = session.getRoom(roomId) + if (room != null) { + GlobalScope.launch { + room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) + } + } } } From b9b755e6e11d8d59afca5491f827a5f292dabe53 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sun, 8 Nov 2020 13:52:09 +0000 Subject: [PATCH 111/249] Convert UserService to suspend functions Signed-off-by: Dominic Fischer --- .../org/matrix/android/sdk/rx/RxSession.kt | 5 +- .../sdk/api/session/user/UserService.kt | 11 ++-- .../session/user/DefaultUserService.kt | 64 +++++-------------- .../home/room/detail/RoomDetailViewModel.kt | 16 ++--- .../matrixto/MatrixToBottomSheetViewModel.kt | 7 +- .../RoomMemberProfileViewModel.kt | 24 ++++--- .../settings/ignored/IgnoredUsersViewModel.kt | 29 +++------ .../usercode/UserCodeSharedViewModel.kt | 7 +- 8 files changed, 54 insertions(+), 109 deletions(-) diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt index a7b269fcc6..33bcad8f4d 100644 --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt @@ -20,6 +20,7 @@ import androidx.paging.PagedList import io.reactivex.Observable import io.reactivex.Single import io.reactivex.functions.Function3 +import kotlinx.coroutines.rx2.rxSingle import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session @@ -129,8 +130,8 @@ class RxSession(private val session: Session) { fun searchUsersDirectory(search: String, limit: Int, - excludedUserIds: Set): Single> = singleBuilder { - session.searchUsersDirectory(search, limit, excludedUserIds, it) + excludedUserIds: Set): Single> = rxSingle { + session.searchUsersDirectory(search, limit, excludedUserIds) } fun joinRoom(roomIdOrAlias: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt index ab85f979bf..cd4fb216d3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt @@ -18,9 +18,7 @@ package org.matrix.android.sdk.api.session.user import androidx.lifecycle.LiveData import androidx.paging.PagedList -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.user.model.User -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional /** @@ -38,17 +36,16 @@ interface UserService { /** * Try to resolve user from known users, or using profile api */ - fun resolveUser(userId: String, callback: MatrixCallback) + suspend fun resolveUser(userId: String): User /** * Search list of users on server directory. * @param search the searched term * @param limit the max number of users to return * @param excludedUserIds the user ids to filter from the search - * @param callback the async callback * @return Cancelable */ - fun searchUsersDirectory(search: String, limit: Int, excludedUserIds: Set, callback: MatrixCallback>): Cancelable + suspend fun searchUsersDirectory(search: String, limit: Int, excludedUserIds: Set): List /** * Observe a live user from a userId @@ -79,10 +76,10 @@ interface UserService { /** * Ignore users */ - fun ignoreUserIds(userIds: List, callback: MatrixCallback): Cancelable + suspend fun ignoreUserIds(userIds: List) /** * Un-ignore some users */ - fun unIgnoreUserIds(userIds: List, callback: MatrixCallback): Cancelable + suspend fun unIgnoreUserIds(userIds: List) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt index 1740956915..52b8cc3689 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt @@ -18,54 +18,35 @@ package org.matrix.android.sdk.internal.session.user import androidx.lifecycle.LiveData import androidx.paging.PagedList -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.user.UserService import org.matrix.android.sdk.api.session.user.model.User -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask import org.matrix.android.sdk.internal.session.user.accountdata.UpdateIgnoredUserIdsTask import org.matrix.android.sdk.internal.session.user.model.SearchUserTask -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith import javax.inject.Inject internal class DefaultUserService @Inject constructor(private val userDataSource: UserDataSource, private val searchUserTask: SearchUserTask, private val updateIgnoredUserIdsTask: UpdateIgnoredUserIdsTask, - private val getProfileInfoTask: GetProfileInfoTask, - private val taskExecutor: TaskExecutor) : UserService { + private val getProfileInfoTask: GetProfileInfoTask) : UserService { override fun getUser(userId: String): User? { return userDataSource.getUser(userId) } - override fun resolveUser(userId: String, callback: MatrixCallback) { + override suspend fun resolveUser(userId: String): User { val known = getUser(userId) if (known != null) { - callback.onSuccess(known) + return known } else { val params = GetProfileInfoTask.Params(userId) - getProfileInfoTask - .configureWith(params) { - this.callback = object : MatrixCallback { - override fun onSuccess(data: JsonDict) { - callback.onSuccess( - User( - userId, - data[ProfileService.DISPLAY_NAME_KEY] as? String, - data[ProfileService.AVATAR_URL_KEY] as? String) - ) - } - - override fun onFailure(failure: Throwable) { - callback.onFailure(failure) - } - } - } - .executeBy(taskExecutor) + val data = getProfileInfoTask.execute(params) + return User( + userId, + data[ProfileService.DISPLAY_NAME_KEY] as? String, + data[ProfileService.AVATAR_URL_KEY] as? String) } } @@ -85,33 +66,20 @@ internal class DefaultUserService @Inject constructor(private val userDataSource return userDataSource.getIgnoredUsersLive() } - override fun searchUsersDirectory(search: String, - limit: Int, - excludedUserIds: Set, - callback: MatrixCallback>): Cancelable { + override suspend fun searchUsersDirectory(search: String, + limit: Int, + excludedUserIds: Set): List { val params = SearchUserTask.Params(limit, search, excludedUserIds) - return searchUserTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + return searchUserTask.execute(params) } - override fun ignoreUserIds(userIds: List, callback: MatrixCallback): Cancelable { + override suspend fun ignoreUserIds(userIds: List) { val params = UpdateIgnoredUserIdsTask.Params(userIdsToIgnore = userIds.toList()) - return updateIgnoredUserIdsTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + updateIgnoredUserIdsTask.execute(params) } - override fun unIgnoreUserIds(userIds: List, callback: MatrixCallback): Cancelable { + override suspend fun unIgnoreUserIds(userIds: List) { val params = UpdateIgnoredUserIdsTask.Params(userIdsToUnIgnore = userIds.toList()) - return updateIgnoredUserIdsTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + updateIgnoredUserIdsTask.execute(params) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index af3d5461ef..2d301a813b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -1275,15 +1275,15 @@ class RoomDetailViewModel @AssistedInject constructor( return } - session.ignoreUserIds(listOf(action.userId), object : MatrixCallback { - override fun onSuccess(data: Unit) { - _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action)) + viewModelScope.launch { + val event = try { + session.ignoreUserIds(listOf(action.userId)) + RoomDetailViewEvents.ActionSuccess(action) + } catch (failure: Throwable) { + RoomDetailViewEvents.ActionFailure(action, failure) } - - override fun onFailure(failure: Throwable) { - _viewEvents.post(RoomDetailViewEvents.ActionFailure(action, failure)) - } - }) + _viewEvents.post(event) + } } private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) { diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt index e5bdc9bca8..b961383575 100644 --- a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheetViewModel.kt @@ -41,7 +41,6 @@ import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.internal.util.awaitCallback class MatrixToBottomSheetViewModel @AssistedInject constructor( @Assisted initialState: MatrixToBottomSheetState, @@ -101,11 +100,7 @@ class MatrixToBottomSheetViewModel @AssistedInject constructor( } private suspend fun resolveUser(userId: String): User { - return tryOrNull { - awaitCallback { - session.resolveUser(userId, it) - } - } + return tryOrNull { session.resolveUser(userId) } // Create raw user in case the user is not searchable ?: User(userId, null, null) } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index 0556b9d2d6..108841faa0 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -37,7 +37,6 @@ import io.reactivex.functions.BiFunction import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType @@ -321,19 +320,18 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v private fun handleIgnoreAction() = withState { state -> val isIgnored = state.isIgnored() ?: return@withState _viewEvents.post(RoomMemberProfileViewEvents.Loading()) - val ignoreActionCallback = object : MatrixCallback { - override fun onSuccess(data: Unit) { - _viewEvents.post(RoomMemberProfileViewEvents.OnIgnoreActionSuccess) + viewModelScope.launch { + val event = try { + if (isIgnored) { + session.unIgnoreUserIds(listOf(state.userId)) + } else { + session.ignoreUserIds(listOf(state.userId)) + } + RoomMemberProfileViewEvents.OnIgnoreActionSuccess + } catch (failure: Throwable) { + RoomMemberProfileViewEvents.Failure(failure) } - - override fun onFailure(failure: Throwable) { - _viewEvents.post(RoomMemberProfileViewEvents.Failure(failure)) - } - } - if (isIgnored) { - session.unIgnoreUserIds(listOf(state.userId), ignoreActionCallback) - } else { - session.ignoreUserIds(listOf(state.userId), ignoreActionCallback) + _viewEvents.post(event) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt index fdc6585829..aa00f71542 100644 --- a/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/IgnoredUsersViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.settings.ignored +import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail import com.airbnb.mvrx.FragmentViewModelContext @@ -30,7 +31,7 @@ import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction -import org.matrix.android.sdk.api.MatrixCallback +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.rx.rx @@ -89,24 +90,14 @@ class IgnoredUsersViewModel @AssistedInject constructor(@Assisted initialState: ) } - session.unIgnoreUserIds(listOf(action.userId), object : MatrixCallback { - override fun onFailure(failure: Throwable) { - setState { - copy( - unIgnoreRequest = Fail(failure) - ) - } - - _viewEvents.post(IgnoredUsersViewEvents.Failure(failure)) + viewModelScope.launch { + val result = runCatching { session.unIgnoreUserIds(listOf(action.userId)) } + setState { + copy( + unIgnoreRequest = result.fold(::Success, ::Fail) + ) } - - override fun onSuccess(data: Unit) { - setState { - copy( - unIgnoreRequest = Success(data) - ) - } - } - }) + result.onFailure { _viewEvents.post(IgnoredUsersViewEvents.Failure(it)) } + } } } diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt index 671d018b63..9637b72581 100644 --- a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt @@ -37,7 +37,6 @@ import org.matrix.android.sdk.api.session.permalinks.PermalinkData import org.matrix.android.sdk.api.session.permalinks.PermalinkParser import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.api.util.toMatrixItem -import org.matrix.android.sdk.internal.util.awaitCallback class UserCodeSharedViewModel @AssistedInject constructor( @Assisted val initialState: UserCodeState, @@ -126,11 +125,7 @@ class UserCodeSharedViewModel @AssistedInject constructor( _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_implemented))) } is PermalinkData.UserLink -> { - val user = tryOrNull { - awaitCallback { - session.resolveUser(linkedId.userId, it) - } - } + val user = tryOrNull { session.resolveUser(linkedId.userId) } // Create raw Uxid in case the user is not searchable ?: User(linkedId.userId, null, null) From 7fbe485603e1ffdd605e62f9235fff194f24aff7 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sat, 27 Mar 2021 20:44:07 +0000 Subject: [PATCH 112/249] Convert PushersService to suspend functions Signed-off-by: Dominic Fischer --- .../sdk/api/session/pushers/PushersService.kt | 13 ++++----- .../session/pushers/DefaultPushersService.kt | 26 +++++------------ .../vector/app/core/pushers/PushersManager.kt | 11 ++++--- ...rSettingsNotificationPreferenceFragment.kt | 29 +++++++++---------- 4 files changed, 31 insertions(+), 48 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt index 3993422b1d..9ea820f5b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt @@ -16,8 +16,6 @@ package org.matrix.android.sdk.api.session.pushers import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.util.Cancelable import java.util.UUID interface PushersService { @@ -75,16 +73,15 @@ interface PushersService { * @param callback callback to know if the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId. * In case of error, PusherRejected failure can happen. In this case it means that the pushkey is not valid. */ - fun testPush(url: String, - appId: String, - pushkey: String, - eventId: String, - callback: MatrixCallback): Cancelable + suspend fun testPush(url: String, + appId: String, + pushkey: String, + eventId: String) /** * Remove the http pusher */ - fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback): Cancelable + suspend fun removeHttpPusher(pushkey: String, appId: String) /** * Get the current pushers, as a LiveData diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt index d290bb1a03..a772cf5ebb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt @@ -18,10 +18,8 @@ package org.matrix.android.sdk.internal.session.pushers import androidx.lifecycle.LiveData import androidx.work.BackoffPolicy import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.pushers.Pusher import org.matrix.android.sdk.api.session.pushers.PushersService -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.PusherEntity import org.matrix.android.sdk.internal.database.query.where @@ -47,16 +45,11 @@ internal class DefaultPushersService @Inject constructor( private val taskExecutor: TaskExecutor ) : PushersService { - override fun testPush(url: String, - appId: String, - pushkey: String, - eventId: String, - callback: MatrixCallback): Cancelable { - return pushGatewayNotifyTask - .configureWith(PushGatewayNotifyTask.Params(url, appId, pushkey, eventId)) { - this.callback = callback - } - .executeBy(taskExecutor) + override suspend fun testPush(url: String, + appId: String, + pushkey: String, + eventId: String) { + pushGatewayNotifyTask.execute(PushGatewayNotifyTask.Params(url, appId, pushkey, eventId)) } override fun refreshPushers() { @@ -102,14 +95,9 @@ internal class DefaultPushersService @Inject constructor( return request.id } - override fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback): Cancelable { + override suspend fun removeHttpPusher(pushkey: String, appId: String) { val params = RemovePusherTask.Params(pushkey, appId) - return removePusherTask - .configureWith(params) { - this.callback = callback - } - // .enableRetry() ?? - .executeBy(taskExecutor) + removePusherTask.execute(params) } override fun getPushersLive(): LiveData> { diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt index 5fe30141d9..dda8b70b08 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt @@ -35,15 +35,14 @@ class PushersManager @Inject constructor( private val stringProvider: StringProvider, private val appNameProvider: AppNameProvider ) { - fun testPush(pushKey: String, callback: MatrixCallback): Cancelable { + suspend fun testPush(pushKey: String) { val currentSession = activeSessionHolder.getActiveSession() - return currentSession.testPush( + currentSession.testPush( stringProvider.getString(R.string.pusher_http_url), stringProvider.getString(R.string.pusher_app_id), pushKey, - TEST_EVENT_ID, - callback + TEST_EVENT_ID ) } @@ -64,9 +63,9 @@ class PushersManager @Inject constructor( ) } - fun unregisterPusher(pushKey: String, callback: MatrixCallback) { + suspend fun unregisterPusher(pushKey: String) { val currentSession = activeSessionHolder.getSafeActiveSession() ?: return - currentSession.removeHttpPusher(pushKey, stringProvider.getString(R.string.pusher_app_id), callback) + currentSession.removeHttpPusher(pushKey, stringProvider.getString(R.string.pusher_app_id)) } companion object { diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt index 47868eed51..fd1f406bcb 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationPreferenceFragment.kt @@ -39,7 +39,6 @@ import im.vector.app.core.utils.requestDisablingBatteryOptimization import im.vector.app.features.notifications.NotificationUtils import im.vector.app.push.fcm.FcmHelper import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.pushrules.RuleIds import org.matrix.android.sdk.api.pushrules.RuleKind @@ -295,20 +294,20 @@ class VectorSettingsNotificationPreferenceFragment @Inject constructor( } } else { FcmHelper.getFcmToken(requireContext())?.let { - pushManager.unregisterPusher(it, object : MatrixCallback { - override fun onSuccess(data: Unit) { - session.refreshPushers() - } - - override fun onFailure(failure: Throwable) { - if (!isAdded) { - return - } - // revert the check box - switchPref.isChecked = !switchPref.isChecked - Toast.makeText(activity, R.string.unknown_error, Toast.LENGTH_SHORT).show() - } - }) + lifecycleScope.launch { + runCatching { pushManager.unregisterPusher(it) } + .fold( + { session.refreshPushers() }, + { + if (!isAdded) { + return@fold + } + // revert the check box + switchPref.isChecked = !switchPref.isChecked + Toast.makeText(activity, R.string.unknown_error, Toast.LENGTH_SHORT).show() + } + ) + } } } } From b70585016cffa6a38819c0ebb969c79942cf6a9c Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sun, 28 Mar 2021 11:50:17 +0100 Subject: [PATCH 113/249] Convert SharedSecretStorageService to suspend functions Signed-off-by: Dominic Fischer --- .../sdk/internal/crypto/ssss/QuadSTests.kt | 107 ++++----- .../SharedSecretStorageService.kt | 31 ++- .../DefaultSharedSecretStorageService.kt | 215 +++++++----------- .../quads/SharedSecureStorageViewModel.kt | 37 ++- .../recover/BackupToQuadSMigrationTask.kt | 63 +++-- .../recover/BootstrapCrossSigningTask.kt | 103 ++++----- 6 files changed, 227 insertions(+), 329 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt index 0489ee179f..eb4773f3c8 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto.ssss import androidx.lifecycle.Observer import androidx.test.ext.junit.runners.AndroidJUnit4 import org.matrix.android.sdk.InstrumentedTest -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.securestorage.EncryptedSecretContent import org.matrix.android.sdk.api.session.securestorage.KeySigner @@ -31,7 +30,6 @@ import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants -import org.matrix.android.sdk.common.TestMatrixCallback import org.matrix.android.sdk.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2 import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService @@ -40,7 +38,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import org.amshove.kluent.shouldBe import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull @@ -70,8 +67,8 @@ class QuadSTests : InstrumentedTest { val TEST_KEY_ID = "my.test.Key" - mTestHelper.doSync { - quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner, it) + mTestHelper.runBlockingTest { + quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner) } // Assert Account data is updated @@ -99,7 +96,9 @@ class QuadSTests : InstrumentedTest { assertNull("Key was not generated from passphrase", parsed.passphrase) // Set as default key - quadS.setDefaultKey(TEST_KEY_ID, object : MatrixCallback {}) + GlobalScope.launch { + quadS.setDefaultKey(TEST_KEY_ID) + } var defaultKeyAccountData: UserAccountDataEvent? = null val defaultDataLock = CountDownLatch(1) @@ -133,12 +132,11 @@ class QuadSTests : InstrumentedTest { // Store a secret val clearSecret = "42".toByteArray().toBase64NoPadding() - mTestHelper.doSync { + mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.storeSecret( "secret.of.life", clearSecret, - listOf(SharedSecretStorageService.KeyRef(null, keySpec)), // default key - it + listOf(SharedSecretStorageService.KeyRef(null, keySpec)) // default key ) } @@ -155,12 +153,11 @@ class QuadSTests : InstrumentedTest { // Try to decrypt?? - val decryptedSecret = mTestHelper.doSync { + val decryptedSecret = mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.getSecret( "secret.of.life", null, // default key - keySpec!!, - it + keySpec!! ) } @@ -176,13 +173,13 @@ class QuadSTests : InstrumentedTest { val TEST_KEY_ID = "my.test.Key" - mTestHelper.doSync { - quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner, it) + mTestHelper.runBlockingTest { + quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner) } // Test that we don't need to wait for an account data sync to access directly the keyid from DB - mTestHelper.doSync { - quadS.setDefaultKey(TEST_KEY_ID, it) + mTestHelper.runBlockingTest { + quadS.setDefaultKey(TEST_KEY_ID) } mTestHelper.signOutAndClose(aliceSession) @@ -198,15 +195,14 @@ class QuadSTests : InstrumentedTest { val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" - mTestHelper.doSync { + mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.storeSecret( "my.secret", mySecretText.toByteArray().toBase64NoPadding(), listOf( SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)), SharedSecretStorageService.KeyRef(keyId2, RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)) - ), - it + ) ) } @@ -219,19 +215,17 @@ class QuadSTests : InstrumentedTest { assertNotNull(encryptedContent?.get(keyId2)) // Assert that can decrypt with both keys - mTestHelper.doSync { + mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.getSecret("my.secret", keyId1, - RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!!, - it + RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!! ) } - mTestHelper.doSync { + mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.getSecret("my.secret", keyId2, - RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!, - it + RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!! ) } @@ -247,50 +241,34 @@ class QuadSTests : InstrumentedTest { val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" - mTestHelper.doSync { + mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.storeSecret( "my.secret", mySecretText.toByteArray().toBase64NoPadding(), - listOf(SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey))), - it + listOf(SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey))) ) } - val decryptCountDownLatch = CountDownLatch(1) - var error = false - aliceSession.sharedSecretStorageService.getSecret("my.secret", - keyId1, - RawBytesKeySpec.fromPassphrase( - "A bad passphrase", - key1Info.content?.passphrase?.salt ?: "", - key1Info.content?.passphrase?.iterations ?: 0, - null), - object : MatrixCallback { - override fun onSuccess(data: String) { - decryptCountDownLatch.countDown() - } - - override fun onFailure(failure: Throwable) { - error = true - decryptCountDownLatch.countDown() - } - } - ) - - mTestHelper.await(decryptCountDownLatch) - - error shouldBe true + mTestHelper.runBlockingTest { + aliceSession.sharedSecretStorageService.getSecret("my.secret", + keyId1, + RawBytesKeySpec.fromPassphrase( + "A bad passphrase", + key1Info.content?.passphrase?.salt ?: "", + key1Info.content?.passphrase?.iterations ?: 0, + null) + ) + } // Now try with correct key - mTestHelper.doSync { + mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.getSecret("my.secret", keyId1, RawBytesKeySpec.fromPassphrase( passphrase, key1Info.content?.passphrase?.salt ?: "", key1Info.content?.passphrase?.iterations ?: 0, - null), - it + null) ) } @@ -321,15 +299,15 @@ class QuadSTests : InstrumentedTest { private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { val quadS = session.sharedSecretStorageService - val creationInfo = mTestHelper.doSync { - quadS.generateKey(keyId, null, keyId, emptyKeySigner, it) + val creationInfo = mTestHelper.runBlockingTest { + quadS.generateKey(keyId, null, keyId, emptyKeySigner) } assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId") if (asDefault) { - mTestHelper.doSync { - quadS.setDefaultKey(keyId, it) + mTestHelper.runBlockingTest { + quadS.setDefaultKey(keyId) } assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID) } @@ -340,21 +318,20 @@ class QuadSTests : InstrumentedTest { private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { val quadS = session.sharedSecretStorageService - val creationInfo = mTestHelper.doSync { + val creationInfo = mTestHelper.runBlockingTest { quadS.generateKeyWithPassphrase( keyId, keyId, passphrase, emptyKeySigner, - null, - it) + null) } assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId") if (asDefault) { - val setDefaultLatch = CountDownLatch(1) - quadS.setDefaultKey(keyId, TestMatrixCallback(setDefaultLatch)) - mTestHelper.await(setDefaultLatch) + mTestHelper.runBlockingTest { + quadS.setDefaultKey(keyId) + } assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt index 37ecf99f9a..721a2bc8af 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.api.session.securestorage -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME @@ -43,13 +42,12 @@ interface SharedSecretStorageService { * @param keyName a human readable name * @param keySigner Used to add a signature to the key (client should check key signature before storing secret) * - * @param callback Get key creation info + * @return key creation info */ - fun generateKey(keyId: String, - key: SsssKeySpec?, - keyName: String, - keySigner: KeySigner?, - callback: MatrixCallback) + suspend fun generateKey(keyId: String, + key: SsssKeySpec?, + keyName: String, + keySigner: KeySigner?): SsssKeyCreationInfo /** * Generates a SSSS key using the given passphrase. @@ -61,14 +59,13 @@ interface SharedSecretStorageService { * @param keySigner Used to add a signature to the key (client should check key signature before retrieving secret) * @param progressListener The derivation of the passphrase may take long depending on the device, use this to report progress * - * @param callback Get key creation info + * @return key creation info */ - fun generateKeyWithPassphrase(keyId: String, - keyName: String, - passphrase: String, - keySigner: KeySigner, - progressListener: ProgressListener?, - callback: MatrixCallback) + suspend fun generateKeyWithPassphrase(keyId: String, + keyName: String, + passphrase: String, + keySigner: KeySigner, + progressListener: ProgressListener?): SsssKeyCreationInfo fun getKey(keyId: String): KeyInfoResult @@ -80,7 +77,7 @@ interface SharedSecretStorageService { */ fun getDefaultKey(): KeyInfoResult - fun setDefaultKey(keyId: String, callback: MatrixCallback) + suspend fun setDefaultKey(keyId: String) /** * Check whether we have a key with a given ID. @@ -98,7 +95,7 @@ interface SharedSecretStorageService { * @param secret The secret contents. * @param keys The list of (ID,privateKey) of the keys to use to encrypt the secret. */ - fun storeSecret(name: String, secretBase64: String, keys: List, callback: MatrixCallback) + suspend fun storeSecret(name: String, secretBase64: String, keys: List) /** * Use this call to determine which SSSSKeySpec to use for requesting secret @@ -113,7 +110,7 @@ interface SharedSecretStorageService { * @param secretKey the secret key to use (@see #RawBytesKeySpec) * */ - fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec, callback: MatrixCallback) + suspend fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec): String /** * Return true if SSSS is configured diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt index 82b5185fe8..6869d5188e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.crypto.secrets -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.accountdata.AccountDataService @@ -43,10 +42,10 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey import org.matrix.android.sdk.internal.crypto.tools.HkdfSha256 import org.matrix.android.sdk.internal.crypto.tools.withOlmDecryption import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.extensions.foldToCallback import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.olm.OlmPkMessage import java.security.SecureRandom import javax.crypto.Cipher @@ -64,21 +63,15 @@ internal class DefaultSharedSecretStorageService @Inject constructor( private val cryptoCoroutineScope: CoroutineScope ) : SharedSecretStorageService { - override fun generateKey(keyId: String, - key: SsssKeySpec?, - keyName: String, - keySigner: KeySigner?, - callback: MatrixCallback) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - val bytes = try { - (key as? RawBytesKeySpec)?.privateKey - ?: ByteArray(32).also { - SecureRandom().nextBytes(it) - } - } catch (failure: Throwable) { - callback.onFailure(failure) - return@launch - } + override suspend fun generateKey(keyId: String, + key: SsssKeySpec?, + keyName: String, + keySigner: KeySigner?): SsssKeyCreationInfo { + return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) { + val bytes = (key as? RawBytesKeySpec)?.privateKey + ?: ByteArray(32).also { + SecureRandom().nextBytes(it) + } val storageKeyContent = SecretStorageKeyContent( name = keyName, @@ -92,34 +85,24 @@ internal class DefaultSharedSecretStorageService @Inject constructor( ) } ?: storageKeyContent - accountDataService.updateAccountData( - "$KEY_ID_BASE.$keyId", - signedContent.toContent(), - object : MatrixCallback { - override fun onFailure(failure: Throwable) { - callback.onFailure(failure) - } - - override fun onSuccess(data: Unit) { - callback.onSuccess(SsssKeyCreationInfo( - keyId = keyId, - content = storageKeyContent, - recoveryKey = computeRecoveryKey(bytes), - keySpec = RawBytesKeySpec(bytes) - )) - } - } + awaitCallback { + accountDataService.updateAccountData("$KEY_ID_BASE.$keyId", signedContent.toContent(), it) + } + SsssKeyCreationInfo( + keyId = keyId, + content = storageKeyContent, + recoveryKey = computeRecoveryKey(bytes), + keySpec = RawBytesKeySpec(bytes) ) } } - override fun generateKeyWithPassphrase(keyId: String, - keyName: String, - passphrase: String, - keySigner: KeySigner, - progressListener: ProgressListener?, - callback: MatrixCallback) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { + override suspend fun generateKeyWithPassphrase(keyId: String, + keyName: String, + passphrase: String, + keySigner: KeySigner, + progressListener: ProgressListener?): SsssKeyCreationInfo { + return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) { val privatePart = generatePrivateKeyWithPassword(passphrase, progressListener) val storageKeyContent = SecretStorageKeyContent( @@ -133,23 +116,18 @@ internal class DefaultSharedSecretStorageService @Inject constructor( ) } ?: storageKeyContent - accountDataService.updateAccountData( - "$KEY_ID_BASE.$keyId", - signedContent.toContent(), - object : MatrixCallback { - override fun onFailure(failure: Throwable) { - callback.onFailure(failure) - } - - override fun onSuccess(data: Unit) { - callback.onSuccess(SsssKeyCreationInfo( - keyId = keyId, - content = storageKeyContent, - recoveryKey = computeRecoveryKey(privatePart.privateKey), - keySpec = RawBytesKeySpec(privatePart.privateKey) - )) - } - } + awaitCallback { + accountDataService.updateAccountData( + "$KEY_ID_BASE.$keyId", + signedContent.toContent(), + it + ) + } + SsssKeyCreationInfo( + keyId = keyId, + content = storageKeyContent, + recoveryKey = computeRecoveryKey(privatePart.privateKey), + keySpec = RawBytesKeySpec(privatePart.privateKey) ) } } @@ -168,15 +146,17 @@ internal class DefaultSharedSecretStorageService @Inject constructor( } ?: KeyInfoResult.Error(SharedSecretStorageError.UnknownAlgorithm(keyId)) } - override fun setDefaultKey(keyId: String, callback: MatrixCallback) { + override suspend fun setDefaultKey(keyId: String) { val existingKey = getKey(keyId) if (existingKey is KeyInfoResult.Success) { - accountDataService.updateAccountData(DEFAULT_KEY_ID, - mapOf("key" to keyId), - callback - ) + awaitCallback { + accountDataService.updateAccountData(DEFAULT_KEY_ID, + mapOf("key" to keyId), + it + ) + } } else { - callback.onFailure(SharedSecretStorageError.UnknownKey(keyId)) + throw SharedSecretStorageError.UnknownKey(keyId) } } @@ -188,41 +168,35 @@ internal class DefaultSharedSecretStorageService @Inject constructor( return getKey(keyId) } - override fun storeSecret(name: String, secretBase64: String, keys: List, callback: MatrixCallback) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { + override suspend fun storeSecret(name: String, secretBase64: String, keys: List) { + withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) { val encryptedContents = HashMap() - try { - keys.forEach { - val keyId = it.keyId - // encrypt the content - when (val key = keyId?.let { getKey(keyId) } ?: getDefaultKey()) { - is KeyInfoResult.Success -> { - if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_AES_HMAC_SHA2) { - encryptAesHmacSha2(it.keySpec!!, name, secretBase64).let { - encryptedContents[key.keyInfo.id] = it - } - } else { - // Unknown algorithm - callback.onFailure(SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: "")) - return@launch + keys.forEach { + val keyId = it.keyId + // encrypt the content + when (val key = keyId?.let { getKey(keyId) } ?: getDefaultKey()) { + is KeyInfoResult.Success -> { + if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_AES_HMAC_SHA2) { + encryptAesHmacSha2(it.keySpec!!, name, secretBase64).let { + encryptedContents[key.keyInfo.id] = it } - } - is KeyInfoResult.Error -> { - callback.onFailure(key.error) - return@launch + } else { + // Unknown algorithm + throw SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: "") } } + is KeyInfoResult.Error -> throw key.error } + } + awaitCallback { accountDataService.updateAccountData( type = name, content = mapOf( "encrypted" to encryptedContents ), - callback = callback + callback = it ) - } catch (failure: Throwable) { - callback.onFailure(failure) } } } @@ -344,57 +318,40 @@ internal class DefaultSharedSecretStorageService @Inject constructor( return results } - override fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec, callback: MatrixCallback) { - val accountData = accountDataService.getAccountDataEvent(name) ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.UnknownSecret(name)) - } - val encryptedContent = accountData.content[ENCRYPTED] as? Map<*, *> ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.SecretNotEncrypted(name)) - } - val key = keyId?.let { getKey(it) } as? KeyInfoResult.Success ?: getDefaultKey() as? KeyInfoResult.Success ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.UnknownKey(name)) - } + override suspend fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec): String { + val accountData = accountDataService.getAccountDataEvent(name) ?: throw SharedSecretStorageError.UnknownSecret(name) + val encryptedContent = accountData.content[ENCRYPTED] as? Map<*, *> ?: throw SharedSecretStorageError.SecretNotEncrypted(name) + val key = keyId?.let { getKey(it) } as? KeyInfoResult.Success ?: getDefaultKey() as? KeyInfoResult.Success + ?: throw SharedSecretStorageError.UnknownKey(name) - val encryptedForKey = encryptedContent[key.keyInfo.id] ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.SecretNotEncryptedWithKey(name, key.keyInfo.id)) - } + val encryptedForKey = encryptedContent[key.keyInfo.id] ?: throw SharedSecretStorageError.SecretNotEncryptedWithKey(name, key.keyInfo.id) val secretContent = EncryptedSecretContent.fromJson(encryptedForKey) - ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.ParsingError) - } + ?: throw SharedSecretStorageError.ParsingError val algorithm = key.keyInfo.content if (SSSS_ALGORITHM_CURVE25519_AES_SHA2 == algorithm.algorithm) { - val keySpec = secretKey as? RawBytesKeySpec ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.BadKeyFormat) - } - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - // decrypt from recovery key - withOlmDecryption { olmPkDecryption -> - olmPkDecryption.setPrivateKey(keySpec.privateKey) - olmPkDecryption.decrypt(OlmPkMessage() - .apply { - mCipherText = secretContent.ciphertext - mEphemeralKey = secretContent.ephemeral - mMac = secretContent.mac - } - ) - } - }.foldToCallback(callback) + val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat + return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) { + // decrypt from recovery key + withOlmDecryption { olmPkDecryption -> + olmPkDecryption.setPrivateKey(keySpec.privateKey) + olmPkDecryption.decrypt(OlmPkMessage() + .apply { + mCipherText = secretContent.ciphertext + mEphemeralKey = secretContent.ephemeral + mMac = secretContent.mac + } + ) + } } } else if (SSSS_ALGORITHM_AES_HMAC_SHA2 == algorithm.algorithm) { - val keySpec = secretKey as? RawBytesKeySpec ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.BadKeyFormat) - } - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - decryptAesHmacSha2(keySpec, name, secretContent) - }.foldToCallback(callback) + val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat + return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) { + decryptAesHmacSha2(keySpec, name, secretContent) } } else { - callback.onFailure(SharedSecretStorageError.UnsupportedAlgorithm(algorithm.algorithm ?: "")) + throw SharedSecretStorageError.UnsupportedAlgorithm(algorithm.algorithm ?: "") } } diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt index e95f250dd3..573a95a7dd 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -43,7 +43,6 @@ import org.matrix.android.sdk.api.session.securestorage.IntegrityResult import org.matrix.android.sdk.api.session.securestorage.KeyInfoResult import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx import timber.log.Timber import java.io.ByteArrayOutputStream @@ -130,13 +129,13 @@ class SharedSecureStorageViewModel @AssistedInject constructor( override fun handle(action: SharedSecureStorageAction) = withState { when (action) { is SharedSecureStorageAction.TogglePasswordVisibility -> handleTogglePasswordVisibility() - is SharedSecureStorageAction.Cancel -> handleCancel() - is SharedSecureStorageAction.SubmitPassphrase -> handleSubmitPassphrase(action) - SharedSecureStorageAction.UseKey -> handleUseKey() - is SharedSecureStorageAction.SubmitKey -> handleSubmitKey(action) - SharedSecureStorageAction.Back -> handleBack() - SharedSecureStorageAction.ForgotResetAll -> handleResetAll() - SharedSecureStorageAction.DoResetAll -> handleDoResetAll() + is SharedSecureStorageAction.Cancel -> handleCancel() + is SharedSecureStorageAction.SubmitPassphrase -> handleSubmitPassphrase(action) + SharedSecureStorageAction.UseKey -> handleUseKey() + is SharedSecureStorageAction.SubmitKey -> handleSubmitKey(action) + SharedSecureStorageAction.Back -> handleBack() + SharedSecureStorageAction.ForgotResetAll -> handleResetAll() + SharedSecureStorageAction.DoResetAll -> handleDoResetAll() }.exhaustive } @@ -220,13 +219,10 @@ class SharedSecureStorageViewModel @AssistedInject constructor( withContext(Dispatchers.IO) { args.requestedSecrets.forEach { if (session.getAccountDataEvent(it) != null) { - val res = awaitCallback { callback -> - session.sharedSecretStorageService.getSecret( - name = it, - keyId = keyInfo.id, - secretKey = keySpec, - callback = callback) - } + val res = session.sharedSecretStorageService.getSecret( + name = it, + keyId = keyInfo.id, + secretKey = keySpec) decryptedSecretMap[it] = res } else { Timber.w("## Cannot find secret $it in SSSS, skip") @@ -292,13 +288,10 @@ class SharedSecureStorageViewModel @AssistedInject constructor( withContext(Dispatchers.IO) { args.requestedSecrets.forEach { if (session.getAccountDataEvent(it) != null) { - val res = awaitCallback { callback -> - session.sharedSecretStorageService.getSecret( - name = it, - keyId = keyInfo.id, - secretKey = keySpec, - callback = callback) - } + val res = session.sharedSecretStorageService.getSecret( + name = it, + keyId = keyInfo.id, + secretKey = keySpec) decryptedSecretMap[it] = res } else { Timber.w("## Cannot find secret $it in SSSS, skip") diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt index 8fbef016cf..74bab9b0b6 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BackupToQuadSMigrationTask.kt @@ -97,37 +97,31 @@ class BackupToQuadSMigrationTask @Inject constructor( when { params.passphrase?.isNotEmpty() == true -> { reportProgress(params, R.string.bootstrap_progress_generating_ssss) - awaitCallback { - quadS.generateKeyWithPassphrase( - UUID.randomUUID().toString(), - "ssss_key", - params.passphrase, - EmptyKeySigner(), - object : ProgressListener { - override fun onProgress(progress: Int, total: Int) { - params.progressListener?.onProgress( - WaitingViewData( - stringProvider.getString( - R.string.bootstrap_progress_generating_ssss_with_info, - "$progress/$total") - )) - } - }, - it - ) - } + quadS.generateKeyWithPassphrase( + UUID.randomUUID().toString(), + "ssss_key", + params.passphrase, + EmptyKeySigner(), + object : ProgressListener { + override fun onProgress(progress: Int, total: Int) { + params.progressListener?.onProgress( + WaitingViewData( + stringProvider.getString( + R.string.bootstrap_progress_generating_ssss_with_info, + "$progress/$total") + )) + } + } + ) } params.recoveryKey != null -> { reportProgress(params, R.string.bootstrap_progress_generating_ssss_recovery) - awaitCallback { - quadS.generateKey( - UUID.randomUUID().toString(), - extractCurveKeyFromRecoveryKey(params.recoveryKey)?.let { RawBytesKeySpec(it) }, - "ssss_key", - EmptyKeySigner(), - it - ) - } + quadS.generateKey( + UUID.randomUUID().toString(), + extractCurveKeyFromRecoveryKey(params.recoveryKey)?.let { RawBytesKeySpec(it) }, + "ssss_key", + EmptyKeySigner() + ) } else -> { return Result.IllegalParams @@ -137,14 +131,11 @@ class BackupToQuadSMigrationTask @Inject constructor( // Ok, so now we have migrated the old keybackup secret as the quadS key // Now we need to store the keybackup key in SSSS in a compatible way reportProgress(params, R.string.bootstrap_progress_storing_in_sss) - awaitCallback { - quadS.storeSecret( - KEYBACKUP_SECRET_SSSS_NAME, - curveKey.toBase64NoPadding(), - listOf(SharedSecretStorageService.KeyRef(info.keyId, info.keySpec)), - it - ) - } + quadS.storeSecret( + KEYBACKUP_SECRET_SSSS_NAME, + curveKey.toBase64NoPadding(), + listOf(SharedSecretStorageService.KeyRef(info.keyId, info.keySpec)) + ) // save for gossiping keysBackupService.saveBackupRecoveryKey(recoveryKey, version.version) diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt index d1a1237463..87074b7a12 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt @@ -126,25 +126,21 @@ class BootstrapCrossSigningTask @Inject constructor( Timber.d("## BootstrapCrossSigningTask: Creating 4S key with pass: ${params.passphrase != null}") try { - keyInfo = awaitCallback { - params.passphrase?.let { passphrase -> - ssssService.generateKeyWithPassphrase( - UUID.randomUUID().toString(), - "ssss_key", - passphrase, - EmptyKeySigner(), - null, - it - ) - } ?: run { - ssssService.generateKey( - UUID.randomUUID().toString(), - params.keySpec, - "ssss_key", - EmptyKeySigner(), - it - ) - } + keyInfo = params.passphrase?.let { passphrase -> + ssssService.generateKeyWithPassphrase( + UUID.randomUUID().toString(), + "ssss_key", + passphrase, + EmptyKeySigner(), + null + ) + } ?: run { + ssssService.generateKey( + UUID.randomUUID().toString(), + params.keySpec, + "ssss_key", + EmptyKeySigner() + ) } } catch (failure: Failure) { Timber.e("## BootstrapCrossSigningTask: Creating 4S - Failed to generate key <${failure.localizedMessage}>") @@ -159,9 +155,7 @@ class BootstrapCrossSigningTask @Inject constructor( Timber.d("## BootstrapCrossSigningTask: Creating 4S - Set default key") try { - awaitCallback { - ssssService.setDefaultKey(keyInfo.keyId, it) - } + ssssService.setDefaultKey(keyInfo.keyId) } catch (failure: Failure) { // Maybe we could just ignore this error? Timber.e("## BootstrapCrossSigningTask: Creating 4S - Set default key error <${failure.localizedMessage}>") @@ -183,13 +177,11 @@ class BootstrapCrossSigningTask @Inject constructor( ) ) Timber.d("## BootstrapCrossSigningTask: Creating 4S - Storing MSK...") - awaitCallback { - ssssService.storeSecret( - MASTER_KEY_SSSS_NAME, - mskPrivateKey, - listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)), it - ) - } + ssssService.storeSecret( + MASTER_KEY_SSSS_NAME, + mskPrivateKey, + listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)) + ) params.progressListener?.onProgress( WaitingViewData( stringProvider.getString(R.string.bootstrap_crosssigning_progress_save_usk), @@ -197,27 +189,22 @@ class BootstrapCrossSigningTask @Inject constructor( ) ) Timber.d("## BootstrapCrossSigningTask: Creating 4S - Storing USK...") - awaitCallback { - ssssService.storeSecret( - USER_SIGNING_KEY_SSSS_NAME, - uskPrivateKey, - listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)), - it - ) - } + ssssService.storeSecret( + USER_SIGNING_KEY_SSSS_NAME, + uskPrivateKey, + listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)), + ) params.progressListener?.onProgress( WaitingViewData( stringProvider.getString(R.string.bootstrap_crosssigning_progress_save_ssk), isIndeterminate = true ) ) Timber.d("## BootstrapCrossSigningTask: Creating 4S - Storing SSK...") - awaitCallback { - ssssService.storeSecret( - SELF_SIGNING_KEY_SSSS_NAME, - sskPrivateKey, - listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)), it - ) - } + ssssService.storeSecret( + SELF_SIGNING_KEY_SSSS_NAME, + sskPrivateKey, + listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)) + ) } catch (failure: Failure) { Timber.e("## BootstrapCrossSigningTask: Creating 4S - Failed to store keys <${failure.localizedMessage}>") // Maybe we could just ignore this error? @@ -265,14 +252,12 @@ class BootstrapCrossSigningTask @Inject constructor( Timber.d("## BootstrapCrossSigningTask: Creating 4S - Save megolm backup key for gossiping") session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version) - awaitCallback { - extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()?.let { secret -> - ssssService.storeSecret( - KEYBACKUP_SECRET_SSSS_NAME, - secret, - listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)), it - ) - } + extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()?.let { secret -> + ssssService.storeSecret( + KEYBACKUP_SECRET_SSSS_NAME, + secret, + listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)) + ) } } else { Timber.d("## BootstrapCrossSigningTask: Creating 4S - Existing megolm backup found") @@ -284,14 +269,12 @@ class BootstrapCrossSigningTask @Inject constructor( } if (isValid) { Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key valid and known") - awaitCallback { - extractCurveKeyFromRecoveryKey(knownMegolmSecret!!.recoveryKey)?.toBase64NoPadding()?.let { secret -> - ssssService.storeSecret( - KEYBACKUP_SECRET_SSSS_NAME, - secret, - listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)), it - ) - } + extractCurveKeyFromRecoveryKey(knownMegolmSecret!!.recoveryKey)?.toBase64NoPadding()?.let { secret -> + ssssService.storeSecret( + KEYBACKUP_SECRET_SSSS_NAME, + secret, + listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)) + ) } } else { Timber.d("## BootstrapCrossSigningTask: Creating 4S - Megolm key is unknown by this session") From 7aba3cff66c5e4a73c41176af599a4fe5b473964 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sun, 28 Mar 2021 11:59:44 +0100 Subject: [PATCH 114/249] Convert AccountDataService to suspend functions Signed-off-by: Dominic Fischer --- .../session/accountdata/AccountDataService.kt | 4 +-- .../DefaultSharedSecretStorageService.kt | 36 ++++++------------- .../accountdata/DefaultAccountDataService.kt | 33 +++++++---------- .../settings/devtools/AccountDataViewModel.kt | 5 +-- .../features/widgets/WidgetPostAPIHandler.kt | 11 +++--- 5 files changed, 30 insertions(+), 59 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt index f5d2a7df3e..5ebeaad3de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt @@ -17,9 +17,7 @@ package org.matrix.android.sdk.api.session.accountdata import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.events.model.Content -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional interface AccountDataService { @@ -48,5 +46,5 @@ interface AccountDataService { /** * Update the account data with the provided type and the provided account data content */ - fun updateAccountData(type: String, content: Content, callback: MatrixCallback? = null): Cancelable + suspend fun updateAccountData(type: String, content: Content) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt index 6869d5188e..1f80ce2c81 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt @@ -45,7 +45,6 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.withContext -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.olm.OlmPkMessage import java.security.SecureRandom import javax.crypto.Cipher @@ -85,9 +84,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor( ) } ?: storageKeyContent - awaitCallback { - accountDataService.updateAccountData("$KEY_ID_BASE.$keyId", signedContent.toContent(), it) - } + accountDataService.updateAccountData("$KEY_ID_BASE.$keyId", signedContent.toContent()) SsssKeyCreationInfo( keyId = keyId, content = storageKeyContent, @@ -116,13 +113,10 @@ internal class DefaultSharedSecretStorageService @Inject constructor( ) } ?: storageKeyContent - awaitCallback { - accountDataService.updateAccountData( - "$KEY_ID_BASE.$keyId", - signedContent.toContent(), - it - ) - } + accountDataService.updateAccountData( + "$KEY_ID_BASE.$keyId", + signedContent.toContent() + ) SsssKeyCreationInfo( keyId = keyId, content = storageKeyContent, @@ -149,12 +143,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor( override suspend fun setDefaultKey(keyId: String) { val existingKey = getKey(keyId) if (existingKey is KeyInfoResult.Success) { - awaitCallback { - accountDataService.updateAccountData(DEFAULT_KEY_ID, - mapOf("key" to keyId), - it - ) - } + accountDataService.updateAccountData(DEFAULT_KEY_ID, mapOf("key" to keyId)) } else { throw SharedSecretStorageError.UnknownKey(keyId) } @@ -189,15 +178,10 @@ internal class DefaultSharedSecretStorageService @Inject constructor( } } - awaitCallback { - accountDataService.updateAccountData( - type = name, - content = mapOf( - "encrypted" to encryptedContents - ), - callback = it - ) - } + accountDataService.updateAccountData( + type = name, + content = mapOf("encrypted" to encryptedContents) + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultAccountDataService.kt index 1f1e987ebf..27db30f3b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultAccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultAccountDataService.kt @@ -18,16 +18,15 @@ package org.matrix.android.sdk.internal.session.user.accountdata import androidx.lifecycle.LiveData import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.accountdata.AccountDataService import org.matrix.android.sdk.api.session.events.model.Content -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.sync.UserAccountDataSyncHandler import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith +import org.matrix.android.sdk.internal.util.awaitCallback import javax.inject.Inject internal class DefaultAccountDataService @Inject constructor( @@ -54,26 +53,18 @@ internal class DefaultAccountDataService @Inject constructor( return accountDataDataSource.getLiveAccountDataEvents(types) } - override fun updateAccountData(type: String, content: Content, callback: MatrixCallback?): Cancelable { - return updateUserAccountDataTask.configureWith(UpdateUserAccountDataTask.AnyParams( - type = type, - any = content - )) { - this.retryCount = 5 - this.callback = object : MatrixCallback { - override fun onSuccess(data: Unit) { - // TODO Move that to the task (but it created a circular dependencies...) - monarchy.runTransactionSync { realm -> - userAccountDataSyncHandler.handleGenericAccountData(realm, type, content) - } - callback?.onSuccess(data) - } - - override fun onFailure(failure: Throwable) { - callback?.onFailure(failure) - } + override suspend fun updateAccountData(type: String, content: Content) { + val params = UpdateUserAccountDataTask.AnyParams(type = type, any = content) + awaitCallback { callback -> + updateUserAccountDataTask.configureWith(params) { + this.retryCount = 5 // TODO: Need to refactor retrying out into a helper method. + this.callback = callback } + .executeBy(taskExecutor) + } + // TODO Move that to the task (but it created a circular dependencies...) + monarchy.runTransactionSync { realm -> + userAccountDataSyncHandler.handleGenericAccountData(realm, type, content) } - .executeBy(taskExecutor) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt index b2200e6a6d..7880e734a5 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataViewModel.kt @@ -32,7 +32,6 @@ import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx data class AccountDataViewState( @@ -58,9 +57,7 @@ class AccountDataViewModel @AssistedInject constructor(@Assisted initialState: A private fun handleDeleteAccountData(action: AccountDataAction.DeleteAccountData) { viewModelScope.launch { - awaitCallback { - session.updateAccountData(action.type, emptyMap(), it) - } + session.updateAccountData(action.type, emptyMap()) } } diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt index 13d49eb20b..daa72fcd32 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt @@ -283,11 +283,12 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo "type" to "m.widget" ) ) - session.updateAccountData( - type = UserAccountDataTypes.TYPE_WIDGETS, - content = addUserWidgetBody, - callback = createWidgetAPICallback(widgetPostAPIMediator, eventData) - ) + launchWidgetAPIAction(widgetPostAPIMediator, eventData) { + session.updateAccountData( + type = UserAccountDataTypes.TYPE_WIDGETS, + content = addUserWidgetBody + ) + } } else { session.widgetService().createRoomWidget( roomId = roomId, From b6f4be289419017cb3b122c5f0e8906119911229 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sun, 28 Mar 2021 13:52:16 +0100 Subject: [PATCH 115/249] Convert FileService to suspend functions Signed-off-by: Dominic Fischer --- .../sdk/api/session/file/FileService.kt | 13 +- .../internal/session/DefaultFileService.kt | 203 ++++++++---------- .../app/core/glide/VectorGlideModelLoader.kt | 32 ++- .../home/room/detail/RoomDetailFragment.kt | 47 ++-- .../home/room/detail/RoomDetailViewModel.kt | 29 +-- .../features/media/BaseAttachmentProvider.kt | 32 +-- .../media/DataAttachmentRoomProvider.kt | 29 ++- .../media/RoomEventsAttachmentProvider.kt | 28 ++- .../features/media/VideoContentRenderer.kt | 94 ++++---- .../uploads/RoomUploadsViewModel.kt | 17 +- 10 files changed, 232 insertions(+), 292 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt index bcdb5ea257..adfdc2498e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt @@ -17,11 +17,9 @@ package org.matrix.android.sdk.api.session.file import android.net.Uri -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent import org.matrix.android.sdk.api.session.room.model.message.getFileName import org.matrix.android.sdk.api.session.room.model.message.getFileUrl -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import java.io.File @@ -41,20 +39,17 @@ interface FileService { * Download a file. * Result will be a decrypted file, stored in the cache folder. url parameter will be used to create unique filename to avoid name collision. */ - fun downloadFile(fileName: String, + suspend fun downloadFile(fileName: String, mimeType: String?, url: String?, - elementToDecrypt: ElementToDecrypt?, - callback: MatrixCallback): Cancelable + elementToDecrypt: ElementToDecrypt?): File - fun downloadFile(messageContent: MessageWithAttachmentContent, - callback: MatrixCallback): Cancelable = + suspend fun downloadFile(messageContent: MessageWithAttachmentContent): File = downloadFile( fileName = messageContent.getFileName(), mimeType = messageContent.mimeType, url = messageContent.getFileUrl(), - elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(), - callback = callback + elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt() ) fun isFileInCache(mxcUrl: String?, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index 07cde3da60..d05ee48c1b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -20,26 +20,20 @@ import android.content.Context import android.net.Uri import android.webkit.MimeTypeMap import androidx.core.content.FileProvider -import arrow.core.Try -import kotlinx.coroutines.launch +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.completeWith import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.file.FileService -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.api.util.NoOpCancellable import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER -import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.md5 -import org.matrix.android.sdk.internal.util.toCancelable import org.matrix.android.sdk.internal.util.writeToFile import timber.log.Timber import java.io.File @@ -53,14 +47,15 @@ internal class DefaultFileService @Inject constructor( private val contentUrlResolver: ContentUrlResolver, @UnauthenticatedWithCertificateWithProgress private val okHttpClient: OkHttpClient, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val taskExecutor: TaskExecutor + private val coroutineDispatchers: MatrixCoroutineDispatchers ) : FileService { // Legacy folder, will be deleted private val legacyFolder = File(sessionCacheDirectory, "MF") + // Folder to store downloaded files (not decrypted) private val downloadFolder = File(sessionCacheDirectory, "F") + // Folder to store decrypted files private val decryptedFolder = File(downloadFolder, "D") @@ -73,134 +68,113 @@ internal class DefaultFileService @Inject constructor( * Retain ongoing downloads to avoid re-downloading and already downloading file * map of mxCurl to callbacks */ - private val ongoing = mutableMapOf>>() + private val ongoing = mutableMapOf>() /** * Download file in the cache folder, and eventually decrypt it * TODO looks like files are copied 3 times */ - override fun downloadFile(fileName: String, - mimeType: String?, - url: String?, - elementToDecrypt: ElementToDecrypt?, - callback: MatrixCallback): Cancelable { - url ?: return NoOpCancellable.also { - callback.onFailure(IllegalArgumentException("url is null")) - } + override suspend fun downloadFile(fileName: String, + mimeType: String?, + url: String?, + elementToDecrypt: ElementToDecrypt?): File { + url ?: throw IllegalArgumentException("url is null") Timber.v("## FileService downloadFile $url") - synchronized(ongoing) { + // TODO: Remove use of `synchronized` in suspend function. + val existingDownload = synchronized(ongoing) { val existing = ongoing[url] if (existing != null) { Timber.v("## FileService downloadFile is already downloading.. ") - existing.add(callback) - return NoOpCancellable + existing } else { // mark as tracked - ongoing[url] = ArrayList() + ongoing[url] = CompletableDeferred() // and proceed to download + null } } - return taskExecutor.executorScope.launch(coroutineDispatchers.main) { - withContext(coroutineDispatchers.io) { - Try { - if (!decryptedFolder.exists()) { - decryptedFolder.mkdirs() - } - // ensure we use unique file name by using URL (mapped to suitable file name) - // Also we need to add extension for the FileProvider, if not it lot's of app that it's - // shared with will not function well (even if mime type is passed in the intent) - getFiles(url, fileName, mimeType, elementToDecrypt != null) - }.flatMap { cachedFiles -> - if (!cachedFiles.file.exists()) { - val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: return@flatMap Try.Failure(IllegalArgumentException("url is null")) + if (existingDownload != null) { + // FIXME If the first downloader cancels then we'll unfortunately be cancelled too. + return existingDownload.await() + } - val request = Request.Builder() - .url(resolvedUrl) - .header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url) - .build() - - val response = try { - okHttpClient.newCall(request).execute() - } catch (e: Throwable) { - return@flatMap Try.Failure(e) - } - - if (!response.isSuccessful) { - return@flatMap Try.Failure(IOException()) - } - - val source = response.body?.source() - ?: return@flatMap Try.Failure(IOException()) - - Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}") - - // Write the file to cache (encrypted version if the file is encrypted) - writeToFile(source.inputStream(), cachedFiles.file) - response.close() - } else { - Timber.v("## FileService: cache hit for $url") - } - - Try.just(cachedFiles) + val result = runCatching { + val cachedFiles = withContext(coroutineDispatchers.io) { + if (!decryptedFolder.exists()) { + decryptedFolder.mkdirs() } - }.flatMap { cachedFiles -> - // Decrypt if necessary - if (cachedFiles.decryptedFile != null) { - if (!cachedFiles.decryptedFile.exists()) { - Timber.v("## FileService: decrypt file") - // Ensure the parent folder exists - cachedFiles.decryptedFile.parentFile?.mkdirs() - val decryptSuccess = cachedFiles.file.inputStream().use { inputStream -> - cachedFiles.decryptedFile.outputStream().buffered().use { outputStream -> - MXEncryptedAttachments.decryptAttachment( - inputStream, - elementToDecrypt, - outputStream - ) - } - } - if (!decryptSuccess) { - return@flatMap Try.Failure(IllegalStateException("Decryption error")) - } - } else { - Timber.v("## FileService: cache hit for decrypted file") + + // ensure we use unique file name by using URL (mapped to suitable file name) + // Also we need to add extension for the FileProvider, if not it lot's of app that it's + // shared with will not function well (even if mime type is passed in the intent) + val cachedFiles = getFiles(url, fileName, mimeType, elementToDecrypt != null) + + if (!cachedFiles.file.exists()) { + val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: throw IllegalArgumentException("url is null") + + val request = Request.Builder() + .url(resolvedUrl) + .header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url) + .build() + + val response = okHttpClient.newCall(request).execute() + + if (!response.isSuccessful) { + throw IOException() } - Try.just(cachedFiles.decryptedFile) + + val source = response.body?.source() ?: throw IOException() + + Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}") + + // Write the file to cache (encrypted version if the file is encrypted) + writeToFile(source.inputStream(), cachedFiles.file) + response.close() } else { - // Clear file - Try.just(cachedFiles.file) + Timber.v("## FileService: cache hit for $url") } - }.fold( - { throwable -> - callback.onFailure(throwable) - // notify concurrent requests - val toNotify = synchronized(ongoing) { - ongoing[url]?.also { - ongoing.remove(url) - } - } - toNotify?.forEach { otherCallbacks -> - tryOrNull { otherCallbacks.onFailure(throwable) } - } - }, - { file -> - callback.onSuccess(file) - // notify concurrent requests - val toNotify = synchronized(ongoing) { - ongoing[url]?.also { - ongoing.remove(url) - } - } - Timber.v("## FileService additional to notify ${toNotify?.size ?: 0} ") - toNotify?.forEach { otherCallbacks -> - tryOrNull { otherCallbacks.onSuccess(file) } + cachedFiles + } + + // Decrypt if necessary + if (cachedFiles.decryptedFile != null) { + if (!cachedFiles.decryptedFile.exists()) { + Timber.v("## FileService: decrypt file") + // Ensure the parent folder exists + cachedFiles.decryptedFile.parentFile?.mkdirs() + val decryptSuccess = cachedFiles.file.inputStream().use { inputStream -> + cachedFiles.decryptedFile.outputStream().buffered().use { outputStream -> + MXEncryptedAttachments.decryptAttachment( + inputStream, + elementToDecrypt, + outputStream + ) } } - ) - }.toCancelable() + if (!decryptSuccess) { + throw IllegalStateException("Decryption error") + } + } else { + Timber.v("## FileService: cache hit for decrypted file") + } + cachedFiles.decryptedFile + } else { + // Clear file + cachedFiles.file + } + } + + // notify concurrent requests + val toNotify = synchronized(ongoing) { ongoing.remove(url) } + result.onSuccess { + Timber.v("## FileService additional to notify is > 0 ") + } + toNotify?.completeWith(result) + + return result.getOrThrow() } fun storeDataFor(mxcUrl: String, @@ -325,6 +299,7 @@ internal class DefaultFileService @Inject constructor( companion object { private const val ENCRYPTED_FILENAME = "encrypted.bin" + // The extension would be added from the mimetype private const val DEFAULT_FILENAME = "file" } diff --git a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt index 81e81bb78a..cbc5effe44 100644 --- a/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt +++ b/vector/src/main/java/im/vector/app/core/glide/VectorGlideModelLoader.kt @@ -28,10 +28,10 @@ import com.bumptech.glide.signature.ObjectKey import im.vector.app.core.extensions.vectorComponent import im.vector.app.core.files.LocalFilesHelper import im.vector.app.features.media.ImageContentRenderer +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import okhttp3.OkHttpClient -import org.matrix.android.sdk.api.MatrixCallback import timber.log.Timber -import java.io.File import java.io.IOException import java.io.InputStream @@ -113,21 +113,19 @@ class VectorGlideDataFetcher(context: Context, callback.onLoadFailed(IllegalArgumentException("No File service")) } // Use the file vector service, will avoid flickering and redownload after upload - fileService.downloadFile( - fileName = data.filename, - mimeType = data.mimeType, - url = data.url, - elementToDecrypt = data.elementToDecrypt, - callback = object : MatrixCallback { - override fun onSuccess(data: File) { - callback.onDataReady(data.inputStream()) - } - - override fun onFailure(failure: Throwable) { - callback.onLoadFailed(failure as? Exception ?: IOException(failure.localizedMessage)) - } - } - ) + GlobalScope.launch { + val result = runCatching { + fileService.downloadFile( + fileName = data.filename, + mimeType = data.mimeType, + url = data.url, + elementToDecrypt = data.elementToDecrypt) + } + result.fold( + { callback.onDataReady(it.inputStream()) }, + { callback.onLoadFailed(it as? Exception ?: IOException(it.localizedMessage)) } + ) + } // val url = contentUrlResolver.resolveFullSize(data.url) // ?: return // 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 64bffcb49c..f116c2b9af 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 @@ -170,12 +170,12 @@ import im.vector.app.features.widgets.WidgetKind import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import nl.dionsegijn.konfetti.models.Shape import nl.dionsegijn.konfetti.models.Size import org.billcarsonfr.jsonviewer.JSonViewerDialog import org.commonmark.parser.Parser -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.events.model.Event @@ -201,7 +201,6 @@ import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode import timber.log.Timber -import java.io.File import java.net.URL import java.util.UUID import java.util.concurrent.TimeUnit @@ -1676,16 +1675,12 @@ class RoomDetailFragment @Inject constructor( if (action.messageContent is MessageTextContent) { shareText(requireContext(), action.messageContent.body) } else if (action.messageContent is MessageWithAttachmentContent) { - session.fileService().downloadFile( - messageContent = action.messageContent, - callback = object : MatrixCallback { - override fun onSuccess(data: File) { - if (isAdded) { - shareMedia(requireContext(), data, getMimeTypeFromUri(requireContext(), data.toUri())) - } - } - } - ) + lifecycleScope.launch { + val data = session.fileService().downloadFile(messageContent = action.messageContent) + if (isAdded) { + shareMedia(requireContext(), data, getMimeTypeFromUri(requireContext(), data.toUri())) + } + } } } @@ -1706,22 +1701,18 @@ class RoomDetailFragment @Inject constructor( sharedActionViewModel.pendingAction = action return } - session.fileService().downloadFile( - messageContent = action.messageContent, - callback = object : MatrixCallback { - override fun onSuccess(data: File) { - if (isAdded) { - saveMedia( - context = requireContext(), - file = data, - title = action.messageContent.body, - mediaMimeType = action.messageContent.mimeType ?: getMimeTypeFromUri(requireContext(), data.toUri()), - notificationUtils = notificationUtils - ) - } - } - } - ) + 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 + ) + } + } } private fun handleActions(action: EventSharedAction) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index af3d5461ef..19912ee02e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -106,7 +106,6 @@ import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap import timber.log.Timber -import java.io.File import java.util.UUID import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean @@ -1140,25 +1139,17 @@ class RoomDetailViewModel @AssistedInject constructor( )) } } else { - session.fileService().downloadFile( - messageContent = action.messageFileContent, - callback = object : MatrixCallback { - override fun onSuccess(data: File) { - _viewEvents.post(RoomDetailViewEvents.DownloadFileState( - action.messageFileContent.mimeType, - data, - null - )) - } + viewModelScope.launch { + val result = runCatching { + session.fileService().downloadFile(messageContent = action.messageFileContent) + } - override fun onFailure(failure: Throwable) { - _viewEvents.post(RoomDetailViewEvents.DownloadFileState( - action.messageFileContent.mimeType, - null, - failure - )) - } - }) + _viewEvents.post(RoomDetailViewEvents.DownloadFileState( + action.messageFileContent.mimeType, + result.getOrNull(), + result.exceptionOrNull() + )) + } } } diff --git a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt index 11b8832c94..103f42e903 100644 --- a/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/BaseAttachmentProvider.kt @@ -31,7 +31,8 @@ import im.vector.lib.attachmentviewer.AttachmentInfo import im.vector.lib.attachmentviewer.AttachmentSourceProvider import im.vector.lib.attachmentviewer.ImageLoaderTarget import im.vector.lib.attachmentviewer.VideoLoaderTarget -import org.matrix.android.sdk.api.MatrixCallback +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.events.model.isVideoMessage import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -152,21 +153,20 @@ abstract class BaseAttachmentProvider( target.onVideoURLReady(info.uid, data.url) } else { target.onVideoFileLoading(info.uid) - fileService.downloadFile( - fileName = data.filename, - mimeType = data.mimeType, - url = data.url, - elementToDecrypt = data.elementToDecrypt, - callback = object : MatrixCallback { - override fun onSuccess(data: File) { - target.onVideoFileReady(info.uid, data) - } - - override fun onFailure(failure: Throwable) { - target.onVideoFileLoadFailed(info.uid) - } - } - ) + GlobalScope.launch { + val result = runCatching { + fileService.downloadFile( + fileName = data.filename, + mimeType = data.mimeType, + url = data.url, + elementToDecrypt = data.elementToDecrypt + ) + } + result.fold( + { target.onVideoFileReady(info.uid, it) }, + { target.onVideoFileLoadFailed(info.uid) } + ) + } } } diff --git a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt index 328d8f943e..d326b8e50a 100644 --- a/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/DataAttachmentRoomProvider.kt @@ -19,7 +19,8 @@ package im.vector.app.features.media import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.resources.StringProvider import im.vector.lib.attachmentviewer.AttachmentInfo -import org.matrix.android.sdk.api.MatrixCallback +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -77,20 +78,16 @@ class DataAttachmentRoomProvider( override fun getFileForSharing(position: Int, callback: (File?) -> Unit) { val item = getItem(position) - fileService.downloadFile( - fileName = item.filename, - mimeType = item.mimeType, - url = item.url, - elementToDecrypt = item.elementToDecrypt, - callback = object : MatrixCallback { - override fun onSuccess(data: File) { - callback(data) - } - - override fun onFailure(failure: Throwable) { - callback(null) - } - } - ) + GlobalScope.launch { + val result = runCatching { + fileService.downloadFile( + fileName = item.filename, + mimeType = item.mimeType, + url = item.url, + elementToDecrypt = item.elementToDecrypt + ) + } + callback(result.getOrNull()) + } } } diff --git a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt index 53c5dac9ad..fd3386826a 100644 --- a/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt +++ b/vector/src/main/java/im/vector/app/features/media/RoomEventsAttachmentProvider.kt @@ -19,7 +19,8 @@ package im.vector.app.features.media import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.resources.StringProvider import im.vector.lib.attachmentviewer.AttachmentInfo -import org.matrix.android.sdk.api.MatrixCallback +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.room.model.message.MessageContent @@ -125,21 +126,16 @@ class RoomEventsAttachmentProvider( val messageContent = timelineEvent.root.getClearContent().toModel() as? MessageWithAttachmentContent ?: return@let - fileService.downloadFile( - fileName = messageContent.body, - mimeType = messageContent.mimeType, - url = messageContent.getFileUrl(), - elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(), - callback = object : MatrixCallback { - override fun onSuccess(data: File) { - callback(data) - } - - override fun onFailure(failure: Throwable) { - callback(null) - } - } - ) + GlobalScope.launch { + val result = runCatching { + fileService.downloadFile( + fileName = messageContent.body, + mimeType = messageContent.mimeType, + url = messageContent.getFileUrl(), + elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt()) + } + callback(result.getOrNull()) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt index 7e4ea15ff0..59b612afb1 100644 --- a/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/media/VideoContentRenderer.kt @@ -25,11 +25,11 @@ import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.files.LocalFilesHelper +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt import timber.log.Timber -import java.io.File import java.net.URLEncoder import javax.inject.Inject @@ -74,28 +74,31 @@ class VideoContentRenderer @Inject constructor(private val localFilesHelper: Loc thumbnailView.isVisible = true loadingView.isVisible = true - activeSessionHolder.getActiveSession().fileService() - .downloadFile( - fileName = data.filename, - mimeType = data.mimeType, - url = data.url, - elementToDecrypt = data.elementToDecrypt, - callback = object : MatrixCallback { - override fun onSuccess(data: File) { - thumbnailView.isVisible = false - loadingView.isVisible = false - videoView.isVisible = true + GlobalScope.launch { + val result = runCatching { + activeSessionHolder.getActiveSession().fileService() + .downloadFile( + fileName = data.filename, + mimeType = data.mimeType, + url = data.url, + elementToDecrypt = data.elementToDecrypt) + } + result.fold( + { data -> + thumbnailView.isVisible = false + loadingView.isVisible = false + videoView.isVisible = true - videoView.setVideoPath(data.path) - videoView.start() - } - - override fun onFailure(failure: Throwable) { - loadingView.isVisible = false - errorView.isVisible = true - errorView.text = errorFormatter.toHumanReadable(failure) - } - }) + videoView.setVideoPath(data.path) + videoView.start() + }, + { + loadingView.isVisible = false + errorView.isVisible = true + errorView.text = errorFormatter.toHumanReadable(it) + } + ) + } } } else { val resolvedUrl = contentUrlResolver.resolveFullSize(data.url) @@ -112,28 +115,31 @@ class VideoContentRenderer @Inject constructor(private val localFilesHelper: Loc thumbnailView.isVisible = true loadingView.isVisible = true - activeSessionHolder.getActiveSession().fileService() - .downloadFile( - fileName = data.filename, - mimeType = data.mimeType, - url = data.url, - elementToDecrypt = null, - callback = object : MatrixCallback { - override fun onSuccess(data: File) { - thumbnailView.isVisible = false - loadingView.isVisible = false - videoView.isVisible = true + GlobalScope.launch { + val result = runCatching { + activeSessionHolder.getActiveSession().fileService() + .downloadFile( + fileName = data.filename, + mimeType = data.mimeType, + url = data.url, + elementToDecrypt = null) + } + result.fold( + { data -> + thumbnailView.isVisible = false + loadingView.isVisible = false + videoView.isVisible = true - videoView.setVideoPath(data.path) - videoView.start() - } - - override fun onFailure(failure: Throwable) { - loadingView.isVisible = false - errorView.isVisible = true - errorView.text = errorFormatter.toHumanReadable(failure) - } - }) + videoView.setVideoPath(data.path) + videoView.start() + }, + { + loadingView.isVisible = false + errorView.isVisible = true + errorView.text = errorFormatter.toHumanReadable(it) + } + ) + } } } } 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 cdf139c7f6..bae4847f7e 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 @@ -32,10 +32,8 @@ import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.message.MessageType -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap -import java.io.File class RoomUploadsViewModel @AssistedInject constructor( @Assisted initialState: RoomUploadsViewState, @@ -130,12 +128,8 @@ class RoomUploadsViewModel @AssistedInject constructor( private fun handleShare(action: RoomUploadsAction.Share) { viewModelScope.launch { try { - val file = awaitCallback { - session.fileService().downloadFile( - messageContent = action.uploadEvent.contentWithAttachmentContent, - callback = it - ) - } + val file = session.fileService().downloadFile( + messageContent = action.uploadEvent.contentWithAttachmentContent) _viewEvents.post(RoomUploadsViewEvents.FileReadyForSharing(file)) } catch (failure: Throwable) { _viewEvents.post(RoomUploadsViewEvents.Failure(failure)) @@ -146,11 +140,8 @@ class RoomUploadsViewModel @AssistedInject constructor( private fun handleDownload(action: RoomUploadsAction.Download) { viewModelScope.launch { try { - val file = awaitCallback { - session.fileService().downloadFile( - messageContent = action.uploadEvent.contentWithAttachmentContent, - callback = it) - } + val file = session.fileService().downloadFile( + messageContent = action.uploadEvent.contentWithAttachmentContent) _viewEvents.post(RoomUploadsViewEvents.FileReadyForSaving(file, action.uploadEvent.contentWithAttachmentContent.body)) } catch (failure: Throwable) { _viewEvents.post(RoomUploadsViewEvents.Failure(failure)) From 8f4dbd2aefb92b5f74c7caabb41a83eae3611221 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Mar 2021 08:30:44 +0000 Subject: [PATCH 116/249] Bump realm-gradle-plugin from 10.3.1 to 10.4.0 Bumps [realm-gradle-plugin](https://github.com/realm/realm-java) from 10.3.1 to 10.4.0. - [Release notes](https://github.com/realm/realm-java/releases) - [Changelog](https://github.com/realm/realm-java/blob/master/CHANGELOG.md) - [Commits](https://github.com/realm/realm-java/compare/v10.3.1...v10.4.0) Signed-off-by: dependabot[bot] --- matrix-sdk-android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 7fb626ab46..60623a14c9 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -9,7 +9,7 @@ buildscript { jcenter() } dependencies { - classpath "io.realm:realm-gradle-plugin:10.3.1" + classpath "io.realm:realm-gradle-plugin:10.4.0" } } From 1d23f1d9e321eb05490812931ed2d4808e9bd366 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 29 Mar 2021 14:06:57 +0200 Subject: [PATCH 117/249] Realm from 10.4.0 and forward are now found on mavenCentral() instead of jcenter(). --- matrix-sdk-android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 60623a14c9..d74678ae55 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -6,7 +6,7 @@ apply plugin: 'realm-android' buildscript { repositories { - jcenter() + mavenCentral() } dependencies { classpath "io.realm:realm-gradle-plugin:10.4.0" From 980d057b4a055874f2c021bffcd328f8c2267841 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Mar 2021 14:46:27 +0000 Subject: [PATCH 118/249] Bump epoxy_version from 4.4.3 to 4.4.4 Bumps `epoxy_version` from 4.4.3 to 4.4.4. Updates `epoxy` from 4.4.3 to 4.4.4 - [Release notes](https://github.com/airbnb/epoxy/releases) - [Changelog](https://github.com/airbnb/epoxy/blob/master/CHANGELOG.md) - [Commits](https://github.com/airbnb/epoxy/compare/4.4.3...4.4.4) Updates `epoxy-glide-preloading` from 4.4.3 to 4.4.4 - [Release notes](https://github.com/airbnb/epoxy/releases) - [Changelog](https://github.com/airbnb/epoxy/blob/master/CHANGELOG.md) - [Commits](https://github.com/airbnb/epoxy/compare/4.4.3...4.4.4) Updates `epoxy-processor` from 4.4.3 to 4.4.4 - [Release notes](https://github.com/airbnb/epoxy/releases) - [Changelog](https://github.com/airbnb/epoxy/blob/master/CHANGELOG.md) - [Commits](https://github.com/airbnb/epoxy/compare/4.4.3...4.4.4) Updates `epoxy-paging` from 4.4.3 to 4.4.4 - [Release notes](https://github.com/airbnb/epoxy/releases) - [Changelog](https://github.com/airbnb/epoxy/blob/master/CHANGELOG.md) - [Commits](https://github.com/airbnb/epoxy/compare/4.4.3...4.4.4) Signed-off-by: dependabot[bot] --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index e3a7b090e3..0674089aaa 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -290,7 +290,7 @@ android { dependencies { - def epoxy_version = '4.4.3' + def epoxy_version = '4.4.4' def fragment_version = '1.3.2' def arrow_version = "0.8.2" def markwon_version = '4.1.2' From d6d4293ea8e87a7b26478034558ddb7883f942c7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 29 Mar 2021 16:50:12 +0200 Subject: [PATCH 119/249] Timeline : remove filtering from SDK --- .../sdk/api/session/room/timeline/Timeline.kt | 6 - .../session/room/timeline/TimelineSettings.kt | 4 - .../database/mapper/TimelineEventMapper.kt | 4 +- .../session/room/timeline/DefaultTimeline.kt | 139 +++---------- .../room/timeline/DefaultTimelineService.kt | 2 - .../session/room/timeline/Extensions.kt | 50 ----- .../timeline/TimelineHiddenReadReceipts.kt | 195 ------------------ .../session/room/timeline/UIEchoManager.kt | 21 +- .../home/room/detail/RoomDetailFragment.kt | 1 - .../home/room/detail/RoomDetailViewModel.kt | 22 +- .../ScrollOnHighlightedEventCallback.kt | 6 +- .../helper/TimelineSettingsFactory.kt | 42 +--- 12 files changed, 51 insertions(+), 441 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/Extensions.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt index 8932d0734e..06c88db831 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt @@ -95,12 +95,6 @@ interface Timeline { */ fun getTimelineEventWithId(eventId: String?): TimelineEvent? - /** - * Returns the first displayable events starting from eventId. - * It does depend on the provided [TimelineSettings]. - */ - fun getFirstDisplayableEventId(eventId: String): String? - interface Listener { /** * Call when the timeline has been updated through pagination or sync. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt index 25c63d6fbc..ceffedb234 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt @@ -24,10 +24,6 @@ data class TimelineSettings( * The initial number of events to retrieve from cache. You might get less events if you don't have loaded enough yet. */ val initialSize: Int, - /** - * Filters for timeline event - */ - val filters: TimelineEventFilters = TimelineEventFilters(), /** * If true, will build read receipts for each event. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt index a2b36ce590..07954650b2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt @@ -25,9 +25,9 @@ import javax.inject.Inject internal class TimelineEventMapper @Inject constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper) { - fun map(timelineEventEntity: TimelineEventEntity, buildReadReceipts: Boolean = true, correctedReadReceipts: List? = null): TimelineEvent { + fun map(timelineEventEntity: TimelineEventEntity, buildReadReceipts: Boolean = true): TimelineEvent { val readReceipts = if (buildReadReceipts) { - correctedReadReceipts ?: timelineEventEntity.readReceipts + timelineEventEntity.readReceipts ?.let { readReceiptsSummaryMapper.map(it) } 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 61f770b956..1ed142ce23 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 @@ -28,7 +28,6 @@ import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -70,14 +69,12 @@ internal class DefaultTimeline( private val paginationTask: PaginationTask, private val timelineEventMapper: TimelineEventMapper, private val settings: TimelineSettings, - private val hiddenReadReceipts: TimelineHiddenReadReceipts, private val timelineInput: TimelineInput, private val eventDecryptor: TimelineEventDecryptor, private val realmSessionProvider: RealmSessionProvider, private val loadRoomMembersTask: LoadRoomMembersTask, private val readReceiptHandler: ReadReceiptHandler ) : Timeline, - TimelineHiddenReadReceipts.Delegate, TimelineInput.Listener, UIEchoManager.Listener { @@ -93,8 +90,7 @@ internal class DefaultTimeline( private val cancelableBag = CancelableBag() private val debouncer = Debouncer(mainHandler) - private lateinit var nonFilteredEvents: RealmResults - private lateinit var filteredEvents: RealmResults + private lateinit var timelineEvents: RealmResults private lateinit var sendingEvents: RealmResults private var prevDisplayIndex: Int? = null @@ -168,16 +164,9 @@ internal class DefaultTimeline( postSnapshot() } - nonFilteredEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll() - filteredEvents = nonFilteredEvents.where() - .filterEventsWithSettings(settings) - .findAll() - nonFilteredEvents.addChangeListener(eventsChangeListener) + timelineEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll() + timelineEvents.addChangeListener(eventsChangeListener) handleInitialLoad() - if (settings.shouldHandleHiddenReadReceipts()) { - hiddenReadReceipts.start(realm, filteredEvents, nonFilteredEvents, this) - } - loadRoomMembersTask .configureWith(LoadRoomMembersTask.Params(roomId)) { this.callback = NoOpMatrixCallback() @@ -205,10 +194,6 @@ internal class DefaultTimeline( } } - private fun TimelineSettings.shouldHandleHiddenReadReceipts(): Boolean { - return buildReadReceipts && (filters.filterEdits || filters.filterTypes) - } - override fun dispose() { if (isStarted.compareAndSet(true, false)) { isReady.set(false) @@ -220,11 +205,8 @@ internal class DefaultTimeline( if (this::sendingEvents.isInitialized) { sendingEvents.removeAllChangeListeners() } - if (this::nonFilteredEvents.isInitialized) { - nonFilteredEvents.removeAllChangeListeners() - } - if (settings.shouldHandleHiddenReadReceipts()) { - hiddenReadReceipts.dispose() + if (this::timelineEvents.isInitialized) { + timelineEvents.removeAllChangeListeners() } clearAllValues() backgroundRealm.getAndSet(null).also { @@ -256,48 +238,6 @@ internal class DefaultTimeline( } } - override fun getFirstDisplayableEventId(eventId: String): String? { - // If the item is built, the id is obviously displayable - val builtIndex = builtEventsIdMap[eventId] - if (builtIndex != null) { - return eventId - } - // Otherwise, we should check if the event is in the db, but is hidden because of filters - return realmSessionProvider.withRealm { localRealm -> - val nonFilteredEvents = buildEventQuery(localRealm) - .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) - .findAll() - - val nonFilteredEvent = nonFilteredEvents.where() - .equalTo(TimelineEventEntityFields.EVENT_ID, eventId) - .findFirst() - - val filteredEvents = nonFilteredEvents.where() - .filterEventsWithSettings(settings) - .findAll() - val isEventInDb = nonFilteredEvent != null - - val isHidden = isEventInDb && filteredEvents.where() - .equalTo(TimelineEventEntityFields.EVENT_ID, eventId) - .findFirst() == null - - if (isHidden) { - val displayIndex = nonFilteredEvent?.displayIndex - if (displayIndex != null) { - // Then we are looking for the first displayable event after the hidden one - val firstDisplayedEvent = filteredEvents.where() - .lessThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, displayIndex) - .findFirst() - firstDisplayedEvent?.eventId - } else { - null - } - } else { - null - } - } - } - override fun hasMoreToLoad(direction: Timeline.Direction): Boolean { return hasMoreInCache(direction) || !hasReachedEnd(direction) } @@ -319,18 +259,6 @@ internal class DefaultTimeline( listeners.clear() } -// TimelineHiddenReadReceipts.Delegate - - override fun rebuildEvent(eventId: String, readReceipts: List): Boolean { - return rebuildEvent(eventId) { te -> - te.copy(readReceipts = readReceipts) - } - } - - override fun onReadReceiptsUpdated() { - postSnapshot() - } - override fun onNewTimelineEvents(roomId: String, eventIds: List) { if (isLive && this.roomId == roomId) { listeners.forEach { @@ -341,18 +269,13 @@ internal class DefaultTimeline( override fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) { if (roomId != this.roomId || !isLive) return - - val postSnapShot = uiEchoManager.onLocalEchoCreated(timelineEvent) - - if (listOf(timelineEvent).filterEventsWithSettings(settings).isNotEmpty()) { - listeners.forEach { + uiEchoManager.onLocalEchoCreated(timelineEvent) + listeners.forEach { + tryOrNull { it.onNewTimelineEvents(listOf(timelineEvent.eventId)) } } - - if (postSnapShot) { - postSnapshot() - } + postSnapshot() } override fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState) { @@ -439,23 +362,21 @@ internal class DefaultTimeline( val builtSendingEvents = mutableListOf() if (hasReachedEnd(Timeline.Direction.FORWARDS) && !hasMoreInCache(Timeline.Direction.FORWARDS)) { uiEchoManager.getInMemorySendingEvents() - .filterSendingEventsTo(builtSendingEvents) + .updateWithUiEchoInto(builtSendingEvents) sendingEvents .filter { timelineEvent -> builtSendingEvents.none { it.eventId == timelineEvent.eventId } } .map { timelineEventMapper.map(it) } - .filterSendingEventsTo(builtSendingEvents) + .updateWithUiEchoInto(builtSendingEvents) } return builtSendingEvents } - private fun List.filterSendingEventsTo(target: MutableList) { + private fun List.updateWithUiEchoInto(target: MutableList) { target.addAll( - // Filter out sending event that are not displayable! - filterEventsWithSettings(settings) - // Get most up to date send state (in memory) - .map { uiEchoManager.updateSentStateWithUiEcho(it) } + // Get most up to date send state (in memory) + map { uiEchoManager.updateSentStateWithUiEcho(it) } ) } @@ -465,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() @@ -487,9 +408,9 @@ internal class DefaultTimeline( var shouldFetchInitialEvent = false val currentInitialEventId = initialEventId val initialDisplayIndex = if (currentInitialEventId == null) { - nonFilteredEvents.firstOrNull()?.displayIndex + timelineEvents.firstOrNull()?.displayIndex } else { - val initialEvent = nonFilteredEvents.where() + val initialEvent = timelineEvents.where() .equalTo(TimelineEventEntityFields.EVENT_ID, initialEventId) .findFirst() @@ -501,7 +422,7 @@ internal class DefaultTimeline( if (currentInitialEventId != null && shouldFetchInitialEvent) { fetchEvent(currentInitialEventId) } else { - val count = filteredEvents.size.coerceAtMost(settings.initialSize) + val count = timelineEvents.size.coerceAtMost(settings.initialSize) if (initialEventId == null) { paginateInternal(initialDisplayIndex, Timeline.Direction.BACKWARDS, count) } else { @@ -541,8 +462,7 @@ internal class DefaultTimeline( val eventEntity = results[index] eventEntity?.eventId?.let { eventId -> postSnapshot = rebuildEvent(eventId) { - val builtEvent = buildTimelineEvent(eventEntity) - listOf(builtEvent).filterEventsWithSettings(settings).firstOrNull() + buildTimelineEvent(eventEntity) } || postSnapshot } } @@ -563,9 +483,9 @@ internal class DefaultTimeline( // We are in the case where event exists, but we do not know the token. // Fetch (again) the last event to get a token val lastKnownEventId = if (direction == Timeline.Direction.FORWARDS) { - nonFilteredEvents.firstOrNull()?.eventId + timelineEvents.firstOrNull()?.eventId } else { - nonFilteredEvents.lastOrNull()?.eventId + timelineEvents.lastOrNull()?.eventId } if (lastKnownEventId == null) { updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) } @@ -636,7 +556,7 @@ internal class DefaultTimeline( * Return the current Chunk */ private fun getLiveChunk(): ChunkEntity? { - return nonFilteredEvents.firstOrNull()?.chunk?.firstOrNull() + return timelineEvents.firstOrNull()?.chunk?.firstOrNull() } /** @@ -680,14 +600,13 @@ internal class DefaultTimeline( val time = System.currentTimeMillis() - start Timber.v("Built ${offsetResults.size} items from db in $time ms") // For the case where wo reach the lastForward chunk - updateLoadingStates(filteredEvents) + updateLoadingStates(timelineEvents) return offsetResults.size } private fun buildTimelineEvent(eventEntity: TimelineEventEntity) = timelineEventMapper.map( timelineEventEntity = eventEntity, - buildReadReceipts = settings.buildReadReceipts, - correctedReadReceipts = hiddenReadReceipts.correctedReadReceipts(eventEntity.eventId) + buildReadReceipts = settings.buildReadReceipts ).let { // eventually enhance with ui echo? (uiEchoManager.decorateEventWithReactionUiEcho(it) ?: it) @@ -699,7 +618,7 @@ internal class DefaultTimeline( private fun getOffsetResults(startDisplayIndex: Int, direction: Timeline.Direction, count: Long): RealmResults { - val offsetQuery = filteredEvents.where() + val offsetQuery = timelineEvents.where() if (direction == Timeline.Direction.BACKWARDS) { offsetQuery .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) @@ -747,7 +666,7 @@ internal class DefaultTimeline( if (isReady.get().not()) { return@post } - updateLoadingStates(filteredEvents) + updateLoadingStates(timelineEvents) val snapshot = createSnapshot() val runnable = Runnable { listeners.forEach { @@ -783,10 +702,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/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index c3714a1303..6b50e2e8de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -52,7 +52,6 @@ internal class DefaultTimelineService @AssistedInject constructor( private val paginationTask: PaginationTask, private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, private val timelineEventMapper: TimelineEventMapper, - private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, private val loadRoomMembersTask: LoadRoomMembersTask, private val readReceiptHandler: ReadReceiptHandler ) : TimelineService { @@ -72,7 +71,6 @@ internal class DefaultTimelineService @AssistedInject constructor( paginationTask = paginationTask, timelineEventMapper = timelineEventMapper, settings = settings, - hiddenReadReceipts = TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings), timelineInput = timelineInput, eventDecryptor = eventDecryptor, fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/Extensions.kt deleted file mode 100644 index b2c8021f3b..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/Extensions.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.room.timeline - -import io.realm.RealmQuery -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.RelationType -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.model.message.MessageContent -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent -import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings -import org.matrix.android.sdk.internal.database.model.TimelineEventEntity -import org.matrix.android.sdk.internal.database.query.filterEvents - -internal fun RealmQuery.filterEventsWithSettings(settings: TimelineSettings): RealmQuery { - return filterEvents(settings.filters) -} - -internal fun List.filterEventsWithSettings(settings: TimelineSettings): List { - return filter { event -> - val filterType = !settings.filters.filterTypes - || settings.filters.allowedTypes.any { it.eventType == event.root.type && (it.stateKey == null || it.stateKey == event.root.senderId) } - if (!filterType) return@filter false - - val filterEdits = if (settings.filters.filterEdits && event.root.getClearType() == EventType.MESSAGE) { - val messageContent = event.root.getClearContent().toModel() - messageContent?.relatesTo?.type != RelationType.REPLACE && messageContent?.relatesTo?.type != RelationType.RESPONSE - } else { - true - } - if (!filterEdits) return@filter false - - val filterRedacted = settings.filters.filterRedacted && event.root.isRedacted() - !filterRedacted - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt deleted file mode 100644 index 0ade8ad3b8..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2020 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.room.timeline - -import android.util.SparseArray -import io.realm.OrderedRealmCollectionChangeListener -import io.realm.Realm -import io.realm.RealmQuery -import io.realm.RealmResults -import org.matrix.android.sdk.api.session.room.model.ReadReceipt -import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings -import org.matrix.android.sdk.internal.database.mapper.ReadReceiptsSummaryMapper -import org.matrix.android.sdk.internal.database.model.EventEntityFields -import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity -import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntityFields -import org.matrix.android.sdk.internal.database.model.TimelineEventEntity -import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields -import org.matrix.android.sdk.internal.database.query.TimelineEventFilter -import org.matrix.android.sdk.internal.database.query.whereInRoom - -/** - * This class is responsible for handling the read receipts for hidden events (check [TimelineSettings] to see filtering). - * When an hidden event has read receipts, we want to transfer these read receipts on the first older displayed event. - * It has to be used in [DefaultTimeline] and we should call the [start] and [dispose] methods to properly handle realm subscription. - */ -internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, - private val roomId: String, - private val settings: TimelineSettings) { - - interface Delegate { - fun rebuildEvent(eventId: String, readReceipts: List): Boolean - fun onReadReceiptsUpdated() - } - - private val correctedReadReceiptsEventByIndex = SparseArray() - private val correctedReadReceiptsByEvent = HashMap>() - - private lateinit var hiddenReadReceipts: RealmResults - private lateinit var nonFilteredEvents: RealmResults - private lateinit var filteredEvents: RealmResults - private lateinit var delegate: Delegate - - private val hiddenReadReceiptsListener = OrderedRealmCollectionChangeListener> { collection, changeSet -> - if (!collection.isLoaded || !collection.isValid) { - return@OrderedRealmCollectionChangeListener - } - var hasChange = false - // Deletion here means we don't have any readReceipts for the given hidden events - changeSet.deletions.forEach { - val eventId = correctedReadReceiptsEventByIndex.get(it, "") - val timelineEvent = filteredEvents.where() - .equalTo(TimelineEventEntityFields.EVENT_ID, eventId) - .findFirst() - - // We are rebuilding the corresponding event with only his own RR - val readReceipts = readReceiptsSummaryMapper.map(timelineEvent?.readReceipts) - hasChange = delegate.rebuildEvent(eventId, readReceipts) || hasChange - } - correctedReadReceiptsEventByIndex.clear() - correctedReadReceiptsByEvent.clear() - for (index in 0 until hiddenReadReceipts.size) { - val summary = hiddenReadReceipts[index] ?: continue - val timelineEvent = summary.timelineEvent?.firstOrNull() ?: continue - val isLoaded = nonFilteredEvents.where() - .equalTo(TimelineEventEntityFields.EVENT_ID, timelineEvent.eventId).findFirst() != null - val displayIndex = timelineEvent.displayIndex - - if (isLoaded) { - // Then we are looking for the first displayable event after the hidden one - val firstDisplayedEvent = filteredEvents.where() - .lessThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, displayIndex) - .findFirst() - - // If we find one, we should - if (firstDisplayedEvent != null) { - correctedReadReceiptsEventByIndex.put(index, firstDisplayedEvent.eventId) - correctedReadReceiptsByEvent - .getOrPut(firstDisplayedEvent.eventId, { - ArrayList(readReceiptsSummaryMapper.map(firstDisplayedEvent.readReceipts)) - }) - .addAll(readReceiptsSummaryMapper.map(summary)) - } - } - } - if (correctedReadReceiptsByEvent.isNotEmpty()) { - correctedReadReceiptsByEvent.forEach { (eventId, correctedReadReceipts) -> - val sortedReadReceipts = correctedReadReceipts.sortedByDescending { - it.originServerTs - } - hasChange = delegate.rebuildEvent(eventId, sortedReadReceipts) || hasChange - } - } - if (hasChange) { - delegate.onReadReceiptsUpdated() - } - } - - /** - * Start the realm query subscription. Has to be called on an HandlerThread - */ - fun start(realm: Realm, - filteredEvents: RealmResults, - nonFilteredEvents: RealmResults, - delegate: Delegate) { - this.filteredEvents = filteredEvents - this.nonFilteredEvents = nonFilteredEvents - this.delegate = delegate - // We are looking for read receipts set on hidden events. - // We only accept those with a timelineEvent (so coming from pagination/sync). - this.hiddenReadReceipts = ReadReceiptsSummaryEntity.whereInRoom(realm, roomId) - .isNotEmpty(ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.`$`) - .isNotEmpty(ReadReceiptsSummaryEntityFields.READ_RECEIPTS.`$`) - .filterReceiptsWithSettings() - .findAllAsync() - .also { it.addChangeListener(hiddenReadReceiptsListener) } - } - - /** - * Dispose the realm query subscription. Has to be called on an HandlerThread - */ - fun dispose() { - if (this::hiddenReadReceipts.isInitialized) { - this.hiddenReadReceipts.removeAllChangeListeners() - } - } - - /** - * Return the current corrected [ReadReceipt] list for an event, or null - */ - fun correctedReadReceipts(eventId: String?): List? { - return correctedReadReceiptsByEvent[eventId] - } - - /** - * We are looking for receipts related to filtered events. So, it's the opposite of [DefaultTimeline.filterEventsWithSettings] method. - */ - private fun RealmQuery.filterReceiptsWithSettings(): RealmQuery { - beginGroup() - var needOr = false - if (settings.filters.filterTypes) { - beginGroup() - // Events: A, B, C, D, (E and S1), F, G, (H and S1), I - // Allowed: A, B, C, (E and S1), G, (H and S2) - // Result: D, F, H, I - settings.filters.allowedTypes.forEachIndexed { index, filter -> - if (filter.stateKey == null) { - notEqualTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.TYPE}", filter.eventType) - } else { - beginGroup() - notEqualTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.TYPE}", filter.eventType) - or() - notEqualTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.STATE_KEY}", filter.stateKey) - endGroup() - } - if (index != settings.filters.allowedTypes.size - 1) { - and() - } - } - endGroup() - needOr = true - } - if (settings.filters.filterUseless) { - if (needOr) or() - equalTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.IS_USELESS}", true) - needOr = true - } - if (settings.filters.filterEdits) { - if (needOr) or() - like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.CONTENT}", TimelineEventFilter.Content.EDIT) - or() - like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.CONTENT}", TimelineEventFilter.Content.RESPONSE) - needOr = true - } - if (settings.filters.filterRedacted) { - if (needOr) or() - like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.UNSIGNED_DATA}", TimelineEventFilter.Unsigned.REDACTED) - } - endGroup() - return this - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt index 67d0d90d77..44326ce1ac 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt @@ -70,15 +70,13 @@ internal class UIEchoManager( return existingState != sendState } - // return true if should update - fun onLocalEchoCreated(timelineEvent: TimelineEvent): Boolean { - var postSnapshot = false + fun onLocalEchoCreated(timelineEvent: TimelineEvent) { // Manage some ui echos (do it before filter because actual event could be filtered out) when (timelineEvent.root.getClearType()) { EventType.REDACTION -> { } - EventType.REACTION -> { + EventType.REACTION -> { val content = timelineEvent.root.content?.toModel() if (RelationType.ANNOTATION == content?.relatesTo?.type) { val reaction = content.relatesTo.key @@ -91,21 +89,14 @@ internal class UIEchoManager( reaction = reaction ) ) - postSnapshot = listener.rebuildEvent(relatedEventID) { + listener.rebuildEvent(relatedEventID) { decorateEventWithReactionUiEcho(it) - } || postSnapshot + } } } } - - // do not add events that would have been filtered - if (listOf(timelineEvent).filterEventsWithSettings(settings).isNotEmpty()) { - Timber.v("On local echo created: ${timelineEvent.eventId}") - inMemorySendingEvents.add(0, timelineEvent) - postSnapshot = true - } - - return postSnapshot + Timber.v("On local echo created: ${timelineEvent.eventId}") + inMemorySendingEvents.add(0, timelineEvent) } fun decorateEventWithReactionUiEcho(timelineEvent: TimelineEvent): TimelineEvent? { 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 f116c2b9af..b2e7004d0f 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 @@ -1205,7 +1205,6 @@ class RoomDetailFragment @Inject constructor( if (summary?.membership == Membership.JOIN) { views.jumpToBottomView.count = summary.notificationCount views.jumpToBottomView.drawBadge = summary.hasUnreadMessages - scrollOnHighlightedEventCallback.timeline = roomDetailViewModel.timeline timelineEventController.update(state) views.inviteView.visibility = View.GONE if (state.tombstoneEvent == null) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 8b8d4fc823..669cf036d1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -1156,16 +1156,15 @@ class RoomDetailViewModel @AssistedInject constructor( private fun handleNavigateToEvent(action: RoomDetailAction.NavigateToEvent) { stopTrackingUnreadMessages() val targetEventId: String = action.eventId - val correctedEventId = timeline.getFirstDisplayableEventId(targetEventId) ?: targetEventId - val indexOfEvent = timeline.getIndexOfEvent(correctedEventId) + val indexOfEvent = timeline.getIndexOfEvent(targetEventId) if (indexOfEvent == null) { // Event is not already in RAM timeline.restartWithEventId(targetEventId) } if (action.highlight) { - setState { copy(highlightedEventId = correctedEventId) } + setState { copy(highlightedEventId = targetEventId) } } - _viewEvents.post(RoomDetailViewEvents.NavigateToEvent(correctedEventId)) + _viewEvents.post(RoomDetailViewEvents.NavigateToEvent(targetEventId)) } private fun handleResendEvent(action: RoomDetailAction.ResendMessage) { @@ -1389,15 +1388,12 @@ class RoomDetailViewModel @AssistedInject constructor( private fun computeUnreadState(events: List, roomSummary: RoomSummary): UnreadState { if (events.isEmpty()) return UnreadState.Unknown val readMarkerIdSnapshot = roomSummary.readMarkerId ?: return UnreadState.Unknown - val firstDisplayableEventId = timeline.getFirstDisplayableEventId(readMarkerIdSnapshot) - val firstDisplayableEventIndex = timeline.getIndexOfEvent(firstDisplayableEventId) - if (firstDisplayableEventId == null || firstDisplayableEventIndex == null) { - return if (timeline.isLive) { - UnreadState.ReadMarkerNotLoaded(readMarkerIdSnapshot) - } else { - UnreadState.Unknown - } - } + val firstDisplayableEventIndex = timeline.getIndexOfEvent(readMarkerIdSnapshot) + ?: return if (timeline.isLive) { + UnreadState.ReadMarkerNotLoaded(readMarkerIdSnapshot) + } else { + UnreadState.Unknown + } for (i in (firstDisplayableEventIndex - 1) downTo 0) { val timelineEvent = events.getOrNull(i) ?: return UnreadState.Unknown val eventId = timelineEvent.root.eventId ?: return UnreadState.Unknown diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnHighlightedEventCallback.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnHighlightedEventCallback.kt index 0eb02f5c75..72f827e469 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnHighlightedEventCallback.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnHighlightedEventCallback.kt @@ -33,8 +33,6 @@ class ScrollOnHighlightedEventCallback(private val recyclerView: RecyclerView, private val scheduledEventId = AtomicReference() - var timeline: Timeline? = null - override fun onInserted(position: Int, count: Int) { scrollIfNeeded() } @@ -45,9 +43,7 @@ class ScrollOnHighlightedEventCallback(private val recyclerView: RecyclerView, private fun scrollIfNeeded() { val eventId = scheduledEventId.get() ?: return - val nonNullTimeline = timeline ?: return - val correctedEventId = nonNullTimeline.getFirstDisplayableEventId(eventId) - val positionToScroll = timelineEventController.searchPositionOfEvent(correctedEventId) + val positionToScroll = timelineEventController.searchPositionOfEvent(eventId) if (positionToScroll != null) { val firstVisibleItem = layoutManager.findFirstCompletelyVisibleItemPosition() val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt index 01c7ad3986..3aee65bf19 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineSettingsFactory.kt @@ -17,48 +17,14 @@ package im.vector.app.features.home.room.detail.timeline.helper import im.vector.app.core.resources.UserPreferencesProvider -import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.room.timeline.EventTypeFilter -import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import javax.inject.Inject -class TimelineSettingsFactory @Inject constructor( - private val userPreferencesProvider: UserPreferencesProvider, - private val session: Session -) { +class TimelineSettingsFactory @Inject constructor(private val userPreferencesProvider: UserPreferencesProvider) { fun create(): TimelineSettings { - return if (userPreferencesProvider.shouldShowHiddenEvents()) { - TimelineSettings( - initialSize = 30, - filters = TimelineEventFilters( - filterEdits = false, - filterRedacted = userPreferencesProvider.shouldShowRedactedMessages().not(), - filterUseless = false, - filterTypes = false), - buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts()) - } else { - val allowedTypes = TimelineDisplayableEvents.DISPLAYABLE_TYPES.createAllowedEventTypeFilters() - TimelineSettings( - initialSize = 30, - filters = TimelineEventFilters( - filterEdits = true, - filterRedacted = userPreferencesProvider.shouldShowRedactedMessages().not(), - filterUseless = true, - filterTypes = true, - allowedTypes = allowedTypes), - buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts()) - } - } - - private fun List.createAllowedEventTypeFilters(): List { - return map { - EventTypeFilter( - eventType = it, - stateKey = if (it == EventType.STATE_ROOM_MEMBER && !userPreferencesProvider.shouldShowRoomMemberStateEvents()) session.myUserId else null - ) - } + return TimelineSettings( + initialSize = 30, + buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts()) } } From c548a3d2fa368f8a3c335d01795a498792081d93 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sat, 27 Mar 2021 19:38:09 +0000 Subject: [PATCH 120/249] Convert WidgetService to suspend functions Signed-off-by: Dominic Fischer --- .../sdk/api/session/widgets/WidgetService.kt | 10 +- .../session/widgets/DefaultWidgetService.kt | 20 +-- .../internal/session/widgets/WidgetManager.kt | 55 +++---- .../home/room/detail/RoomDetailViewModel.kt | 138 +++++++++--------- .../features/widgets/WidgetPostAPIHandler.kt | 13 +- .../app/features/widgets/WidgetViewModel.kt | 19 +-- 6 files changed, 115 insertions(+), 140 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt index bf3ff8959d..8f35ff0e4a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt @@ -17,10 +17,8 @@ package org.matrix.android.sdk.api.session.widgets import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Content -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.session.widgets.model.Widget /** @@ -107,20 +105,16 @@ interface WidgetService { * @param roomId the room where you want to create the widget. * @param widgetId the widget to create. * @param content the content of the widget - * @param callback the matrix callback to listen for result. - * @return Cancelable */ - fun createRoomWidget(roomId: String, widgetId: String, content: Content, callback: MatrixCallback): Cancelable + suspend fun createRoomWidget(roomId: String, widgetId: String, content: Content): Widget /** * Deactivate a widget in a room. It makes sure you have the rights to handle this. * * @param roomId: the room where you want to deactivate the widget. * @param widgetId: the widget to deactivate. - * @param callback the matrix callback to listen for result. - * @return Cancelable */ - fun destroyRoomWidget(roomId: String, widgetId: String, callback: MatrixCallback): Cancelable + suspend fun destroyRoomWidget(roomId: String, widgetId: String) /** * Returns true if you can add/remove widgets. It goes through diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt index 9f5a9360ee..5912dc7b53 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt @@ -17,14 +17,12 @@ package org.matrix.android.sdk.internal.session.widgets import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.widgets.WidgetPostAPIMediator import org.matrix.android.sdk.api.session.widgets.WidgetService import org.matrix.android.sdk.api.session.widgets.WidgetURLFormatter import org.matrix.android.sdk.api.session.widgets.model.Widget -import org.matrix.android.sdk.api.util.Cancelable import javax.inject.Inject import javax.inject.Provider @@ -77,21 +75,19 @@ internal class DefaultWidgetService @Inject constructor(private val widgetManage return widgetManager.getUserWidgets(widgetTypes, excludedTypes) } - override fun createRoomWidget( + override suspend fun createRoomWidget( roomId: String, widgetId: String, - content: Content, - callback: MatrixCallback - ): Cancelable { - return widgetManager.createRoomWidget(roomId, widgetId, content, callback) + content: Content + ): Widget { + return widgetManager.createRoomWidget(roomId, widgetId, content) } - override fun destroyRoomWidget( + override suspend fun destroyRoomWidget( roomId: String, - widgetId: String, - callback: MatrixCallback - ): Cancelable { - return widgetManager.destroyRoomWidget(roomId, widgetId, callback) + widgetId: String + ) { + return widgetManager.destroyRoomWidget(roomId, widgetId) } override fun hasPermissionsToHandleWidgets(roomId: String): Boolean { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt index 73a4cc697d..3244212487 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt @@ -21,7 +21,6 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes @@ -34,7 +33,6 @@ import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure import org.matrix.android.sdk.api.session.widgets.model.Widget -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.SessionLifecycleObserver import org.matrix.android.sdk.internal.session.SessionScope @@ -43,8 +41,6 @@ import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory import org.matrix.android.sdk.internal.session.widgets.helper.extractWidgetSequence -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.launchToCallback import java.util.HashMap import javax.inject.Inject @@ -52,7 +48,6 @@ import javax.inject.Inject internal class WidgetManager @Inject constructor(private val integrationManager: IntegrationManager, private val accountDataDataSource: AccountDataDataSource, private val stateEventDataSource: StateEventDataSource, - private val taskExecutor: TaskExecutor, private val createWidgetTask: CreateWidgetTask, private val widgetFactory: WidgetFactory, @UserId private val userId: String) @@ -165,37 +160,33 @@ internal class WidgetManager @Inject constructor(private val integrationManager: .toList() } - fun createRoomWidget(roomId: String, widgetId: String, content: Content, callback: MatrixCallback): Cancelable { - return taskExecutor.executorScope.launchToCallback(callback = callback) { - if (!hasPermissionsToHandleWidgets(roomId)) { - throw WidgetManagementFailure.NotEnoughPower - } - val params = CreateWidgetTask.Params( - roomId = roomId, - widgetId = widgetId, - content = content - ) - createWidgetTask.execute(params) - try { - getRoomWidgets(roomId, widgetId = QueryStringValue.Equals(widgetId, QueryStringValue.Case.INSENSITIVE)).first() - } catch (failure: Throwable) { - throw WidgetManagementFailure.CreationFailed - } + suspend fun createRoomWidget(roomId: String, widgetId: String, content: Content): Widget { + if (!hasPermissionsToHandleWidgets(roomId)) { + throw WidgetManagementFailure.NotEnoughPower + } + val params = CreateWidgetTask.Params( + roomId = roomId, + widgetId = widgetId, + content = content + ) + createWidgetTask.execute(params) + try { + return getRoomWidgets(roomId, widgetId = QueryStringValue.Equals(widgetId, QueryStringValue.Case.INSENSITIVE)).first() + } catch (failure: Throwable) { + throw WidgetManagementFailure.CreationFailed } } - fun destroyRoomWidget(roomId: String, widgetId: String, callback: MatrixCallback): Cancelable { - return taskExecutor.executorScope.launchToCallback(callback = callback) { - if (!hasPermissionsToHandleWidgets(roomId)) { - throw WidgetManagementFailure.NotEnoughPower - } - val params = CreateWidgetTask.Params( - roomId = roomId, - widgetId = widgetId, - content = emptyMap() - ) - createWidgetTask.execute(params) + suspend fun destroyRoomWidget(roomId: String, widgetId: String) { + if (!hasPermissionsToHandleWidgets(roomId)) { + throw WidgetManagementFailure.NotEnoughPower } + val params = CreateWidgetTask.Params( + roomId = roomId, + widgetId = widgetId, + content = emptyMap() + ) + createWidgetTask.execute(params) } fun hasPermissionsToHandleWidgets(roomId: String): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index af3d5461ef..adae933e58 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -97,12 +97,10 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.session.room.timeline.getRelationContent import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent -import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.WidgetType import org.matrix.android.sdk.api.util.appendParamToUrl import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap import timber.log.Timber @@ -262,68 +260,68 @@ class RoomDetailViewModel @AssistedInject constructor( override fun handle(action: RoomDetailAction) { when (action) { - is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action) - is RoomDetailAction.ComposerFocusChange -> handleComposerFocusChange(action) - is RoomDetailAction.SaveDraft -> handleSaveDraft(action) - is RoomDetailAction.SendMessage -> handleSendMessage(action) - is RoomDetailAction.SendMedia -> handleSendMedia(action) - is RoomDetailAction.SendSticker -> handleSendSticker(action) - is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action) - is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action) - is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action) - is RoomDetailAction.SendReaction -> handleSendReaction(action) - is RoomDetailAction.AcceptInvite -> handleAcceptInvite() - is RoomDetailAction.RejectInvite -> handleRejectInvite() - is RoomDetailAction.RedactAction -> handleRedactEvent(action) - is RoomDetailAction.UndoReaction -> handleUndoReact(action) - is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action) - is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action) - is RoomDetailAction.EnterEditMode -> handleEditAction(action) - is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action) - is RoomDetailAction.EnterReplyMode -> handleReplyAction(action) - is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action) - is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action) - is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action) - is RoomDetailAction.ResendMessage -> handleResendEvent(action) - is RoomDetailAction.RemoveFailedEcho -> handleRemove(action) - is RoomDetailAction.ResendAll -> handleResendAll() - is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead() - is RoomDetailAction.ReportContent -> handleReportContent(action) - is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action) + is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action) + is RoomDetailAction.ComposerFocusChange -> handleComposerFocusChange(action) + is RoomDetailAction.SaveDraft -> handleSaveDraft(action) + is RoomDetailAction.SendMessage -> handleSendMessage(action) + is RoomDetailAction.SendMedia -> handleSendMedia(action) + is RoomDetailAction.SendSticker -> handleSendSticker(action) + is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action) + is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action) + is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action) + is RoomDetailAction.SendReaction -> handleSendReaction(action) + is RoomDetailAction.AcceptInvite -> handleAcceptInvite() + is RoomDetailAction.RejectInvite -> handleRejectInvite() + is RoomDetailAction.RedactAction -> handleRedactEvent(action) + is RoomDetailAction.UndoReaction -> handleUndoReact(action) + is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action) + is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action) + is RoomDetailAction.EnterEditMode -> handleEditAction(action) + is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action) + is RoomDetailAction.EnterReplyMode -> handleReplyAction(action) + is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action) + is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action) + is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action) + is RoomDetailAction.ResendMessage -> handleResendEvent(action) + is RoomDetailAction.RemoveFailedEcho -> handleRemove(action) + is RoomDetailAction.ResendAll -> handleResendAll() + is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead() + is RoomDetailAction.ReportContent -> handleReportContent(action) + is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action) is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages() - is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages() - is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action) - is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action) - is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) - is RoomDetailAction.RequestVerification -> handleRequestVerification(action) - is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action) - is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action) - is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action) - is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment() - is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager() - is RoomDetailAction.StartCallWithPhoneNumber -> handleStartCallWithPhoneNumber(action) - is RoomDetailAction.StartCall -> handleStartCall(action) - is RoomDetailAction.AcceptCall -> handleAcceptCall(action) - is RoomDetailAction.EndCall -> handleEndCall() - is RoomDetailAction.ManageIntegrations -> handleManageIntegrations() - is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action) - is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId) - is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action) - is RoomDetailAction.CancelSend -> handleCancel(action) - is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action) - is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action) - RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople() - RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar() - is RoomDetailAction.SetAvatarAction -> handleSetNewAvatar(action) - RoomDetailAction.QuickActionSetTopic -> _viewEvents.post(RoomDetailViewEvents.OpenRoomSettings) - is RoomDetailAction.ShowRoomAvatarFullScreen -> { + is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages() + is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action) + is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action) + is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) + is RoomDetailAction.RequestVerification -> handleRequestVerification(action) + is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action) + is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action) + is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action) + is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment() + is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager() + is RoomDetailAction.StartCallWithPhoneNumber -> handleStartCallWithPhoneNumber(action) + is RoomDetailAction.StartCall -> handleStartCall(action) + is RoomDetailAction.AcceptCall -> handleAcceptCall(action) + is RoomDetailAction.EndCall -> handleEndCall() + is RoomDetailAction.ManageIntegrations -> handleManageIntegrations() + is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action) + is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId) + is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action) + is RoomDetailAction.CancelSend -> handleCancel(action) + is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action) + is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action) + RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople() + RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar() + is RoomDetailAction.SetAvatarAction -> handleSetNewAvatar(action) + RoomDetailAction.QuickActionSetTopic -> _viewEvents.post(RoomDetailViewEvents.OpenRoomSettings) + is RoomDetailAction.ShowRoomAvatarFullScreen -> { _viewEvents.post( RoomDetailViewEvents.ShowRoomAvatarFullScreen(action.matrixItem, action.transitionView) ) } - is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action) - RoomDetailAction.RemoveAllFailedMessages -> handleRemoveAllFailedMessages() - RoomDetailAction.ResendAll -> handleResendAll() + is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action) + RoomDetailAction.RemoveAllFailedMessages -> handleRemoveAllFailedMessages() + RoomDetailAction.ResendAll -> handleResendAll() }.exhaustive } @@ -483,9 +481,7 @@ class RoomDetailViewModel @AssistedInject constructor( ) try { - val widget = awaitCallback { - session.widgetService().createRoomWidget(roomId, widgetId, widgetEventContent, it) - } + val widget = session.widgetService().createRoomWidget(roomId, widgetId, widgetEventContent) _viewEvents.post(RoomDetailViewEvents.JoinJitsiConference(widget, action.withVideo)) } catch (failure: Throwable) { _viewEvents.post(RoomDetailViewEvents.ShowMessage(stringProvider.getString(R.string.failed_to_add_widget))) @@ -499,7 +495,7 @@ class RoomDetailViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.ShowWaitingView) viewModelScope.launch(Dispatchers.IO) { try { - awaitCallback { session.widgetService().destroyRoomWidget(room.roomId, widgetId, it) } + session.widgetService().destroyRoomWidget(room.roomId, widgetId) // local echo setState { copy( @@ -663,13 +659,13 @@ class RoomDetailViewModel @AssistedInject constructor( } when (itemId) { R.id.timeline_setting -> true - R.id.invite -> state.canInvite + R.id.invite -> state.canInvite R.id.open_matrix_apps -> true R.id.voice_call, - R.id.video_call -> callManager.getCallsByRoomId(state.roomId).isEmpty() - R.id.hangup_call -> callManager.getCallsByRoomId(state.roomId).isNotEmpty() - R.id.search -> true - R.id.dev_tools -> vectorPreferences.developerMode() + R.id.video_call -> callManager.getCallsByRoomId(state.roomId).isEmpty() + R.id.hangup_call -> callManager.getCallsByRoomId(state.roomId).isNotEmpty() + R.id.search -> true + R.id.dev_tools -> vectorPreferences.developerMode() else -> false } } @@ -816,7 +812,7 @@ class RoomDetailViewModel @AssistedInject constructor( } }.exhaustive } - is SendMode.EDIT -> { + is SendMode.EDIT -> { // is original event a reply? val inReplyTo = state.sendMode.timelineEvent.getRelationContent()?.inReplyTo?.eventId if (inReplyTo != null) { @@ -839,7 +835,7 @@ class RoomDetailViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.MessageSent) popDraft() } - is SendMode.QUOTE -> { + is SendMode.QUOTE -> { val messageContent = state.sendMode.timelineEvent.getLastMessageContent() val textMsg = messageContent?.body @@ -860,7 +856,7 @@ class RoomDetailViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.MessageSent) popDraft() } - is SendMode.REPLY -> { + is SendMode.REPLY -> { state.sendMode.timelineEvent.let { room.replyToMessage(it, action.text.toString(), action.autoMarkdown) _viewEvents.post(RoomDetailViewEvents.MessageSent) diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt index 13d49eb20b..e20c8cf4c6 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt @@ -289,12 +289,13 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo callback = createWidgetAPICallback(widgetPostAPIMediator, eventData) ) } else { - session.widgetService().createRoomWidget( - roomId = roomId, - widgetId = widgetId, - content = widgetEventContent, - callback = createWidgetAPICallback(widgetPostAPIMediator, eventData) - ) + launchWidgetAPIAction(widgetPostAPIMediator, eventData) { + session.widgetService().createRoomWidget( + roomId = roomId, + widgetId = widgetId, + content = widgetEventContent + ) + } } } diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt index d4e63b1338..0388b7b495 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt @@ -41,7 +41,6 @@ import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerS import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.mapOptional import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap @@ -147,13 +146,13 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi override fun handle(action: WidgetAction) { when (action) { - is WidgetAction.OnWebViewLoadingError -> handleWebViewLoadingError(action) + is WidgetAction.OnWebViewLoadingError -> handleWebViewLoadingError(action) is WidgetAction.OnWebViewLoadingSuccess -> handleWebViewLoadingSuccess(action) - is WidgetAction.OnWebViewStartedToLoad -> handleWebViewStartLoading() - WidgetAction.LoadFormattedUrl -> loadFormattedUrl(forceFetchToken = false) - WidgetAction.DeleteWidget -> handleDeleteWidget() - WidgetAction.RevokeWidget -> handleRevokeWidget() - WidgetAction.OnTermsReviewed -> loadFormattedUrl(forceFetchToken = false) + is WidgetAction.OnWebViewStartedToLoad -> handleWebViewStartLoading() + WidgetAction.LoadFormattedUrl -> loadFormattedUrl(forceFetchToken = false) + WidgetAction.DeleteWidget -> handleDeleteWidget() + WidgetAction.RevokeWidget -> handleRevokeWidget() + WidgetAction.OnTermsReviewed -> loadFormattedUrl(forceFetchToken = false) } } @@ -173,10 +172,8 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi viewModelScope.launch { val widgetId = initialState.widgetId ?: return@launch try { - awaitCallback { - widgetService.destroyRoomWidget(initialState.roomId, widgetId, it) - _viewEvents.post(WidgetViewEvents.Close()) - } + widgetService.destroyRoomWidget(initialState.roomId, widgetId) + _viewEvents.post(WidgetViewEvents.Close()) } catch (failure: Throwable) { _viewEvents.post(WidgetViewEvents.Failure(failure)) } From e23cba1d2011b2531bafe1a3d7799b2b7ed596d4 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sat, 27 Mar 2021 20:14:59 +0000 Subject: [PATCH 121/249] Convert ProfileService to suspend functions Signed-off-by: Dominic Fischer --- .../org/matrix/android/sdk/rx/RxSession.kt | 5 +- .../sdk/api/session/profile/ProfileService.kt | 26 ++-- .../session/profile/DefaultProfileService.kt | 126 +++++------------- .../home/room/detail/RoomDetailViewModel.kt | 4 +- .../settings/VectorSettingsGeneralFragment.kt | 46 +++---- .../threepids/ThreePidsSettingsViewModel.kt | 82 ++++++------ 6 files changed, 108 insertions(+), 181 deletions(-) diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt index a7b269fcc6..150a9dc9aa 100644 --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt +++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt @@ -20,6 +20,7 @@ import androidx.paging.PagedList import io.reactivex.Observable import io.reactivex.Single import io.reactivex.functions.Function3 +import kotlinx.coroutines.rx2.rxSingle import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session @@ -144,8 +145,8 @@ class RxSession(private val session: Session) { session.getRoomIdByAlias(roomAlias, searchOnServer, it) } - fun getProfileInfo(userId: String): Single = singleBuilder { - session.getProfile(userId, it) + fun getProfileInfo(userId: String): Single = rxSingle { + session.getProfile(userId) } fun liveUserCryptoDevices(userId: String): Observable> { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt index a4d5b665c6..e493adeaf2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt @@ -19,10 +19,8 @@ package org.matrix.android.sdk.api.session.profile import android.net.Uri import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.session.identity.ThreePid -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.Optional @@ -41,14 +39,14 @@ interface ProfileService { * @param userId the userId param to look for * */ - fun getDisplayName(userId: String, matrixCallback: MatrixCallback>): Cancelable + suspend fun getDisplayName(userId: String): Optional /** * Update the display name for this user * @param userId the userId to update the display name of * @param newDisplayName the new display name of the user */ - fun setDisplayName(userId: String, newDisplayName: String, matrixCallback: MatrixCallback): Cancelable + suspend fun setDisplayName(userId: String, newDisplayName: String) /** * Update the avatar for this user @@ -56,14 +54,14 @@ interface ProfileService { * @param newAvatarUri the new avatar uri of the user * @param fileName the fileName of selected image */ - fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String, matrixCallback: MatrixCallback): Cancelable + suspend fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String) /** * Return the current avatarUrl for this user. * @param userId the userId param to look for * */ - fun getAvatarUrl(userId: String, matrixCallback: MatrixCallback>): Cancelable + suspend fun getAvatarUrl(userId: String): Optional /** * Get the combined profile information for this user. @@ -71,7 +69,7 @@ interface ProfileService { * @param userId the userId param to look for * */ - fun getProfile(userId: String, matrixCallback: MatrixCallback): Cancelable + suspend fun getProfile(userId: String): JsonDict /** * Get the current user 3Pids @@ -97,28 +95,26 @@ interface ProfileService { /** * Add a 3Pids. This is the first step to add a ThreePid to an account. Then the threePid will be added to the pending threePid list. */ - fun addThreePid(threePid: ThreePid, matrixCallback: MatrixCallback): Cancelable + suspend fun addThreePid(threePid: ThreePid) /** * Validate a code received by text message */ - fun submitSmsCode(threePid: ThreePid.Msisdn, code: String, matrixCallback: MatrixCallback): Cancelable + suspend fun submitSmsCode(threePid: ThreePid.Msisdn, code: String) /** * Finalize adding a 3Pids. Call this method once the user has validated that he owns the ThreePid */ - fun finalizeAddingThreePid(threePid: ThreePid, - userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, - matrixCallback: MatrixCallback): Cancelable + suspend fun finalizeAddingThreePid(threePid: ThreePid, + userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) /** * Cancel adding a threepid. It will remove locally stored data about this ThreePid */ - fun cancelAddingThreePid(threePid: ThreePid, - matrixCallback: MatrixCallback): Cancelable + suspend fun cancelAddingThreePid(threePid: ThreePid) /** * Remove a 3Pid from the Matrix account. */ - fun deleteThreePid(threePid: ThreePid, matrixCallback: MatrixCallback): Cancelable + suspend fun deleteThreePid(threePid: ThreePid) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt index b3216d744d..386fec8256 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt @@ -21,11 +21,10 @@ import android.net.Uri import androidx.lifecycle.LiveData import com.zhuinden.monarchy.Monarchy import io.realm.kotlin.where -import org.matrix.android.sdk.api.MatrixCallback +import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.profile.ProfileService -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.api.util.Optional @@ -36,7 +35,6 @@ import org.matrix.android.sdk.internal.session.content.FileUploader import org.matrix.android.sdk.internal.session.user.UserStore import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith -import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import javax.inject.Inject @@ -55,64 +53,38 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto private val userStore: UserStore, private val fileUploader: FileUploader) : ProfileService { - override fun getDisplayName(userId: String, matrixCallback: MatrixCallback>): Cancelable { + override suspend fun getDisplayName(userId: String): Optional { val params = GetProfileInfoTask.Params(userId) - return getProfileInfoTask - .configureWith(params) { - this.callback = object : MatrixCallback { - override fun onSuccess(data: JsonDict) { - val displayName = data[ProfileService.DISPLAY_NAME_KEY] as? String - matrixCallback.onSuccess(Optional.from(displayName)) - } - - override fun onFailure(failure: Throwable) { - matrixCallback.onFailure(failure) - } - } - } - .executeBy(taskExecutor) + val data = getProfileInfoTask.execute(params) + val displayName = data[ProfileService.DISPLAY_NAME_KEY] as? String + return Optional.from(displayName) } - override fun setDisplayName(userId: String, newDisplayName: String, matrixCallback: MatrixCallback): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.io, matrixCallback) { + override suspend fun setDisplayName(userId: String, newDisplayName: String) { + withContext(coroutineDispatchers.io) { setDisplayNameTask.execute(SetDisplayNameTask.Params(userId = userId, newDisplayName = newDisplayName)) userStore.updateDisplayName(userId, newDisplayName) } } - override fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String, matrixCallback: MatrixCallback): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, matrixCallback) { + override suspend fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String) { + withContext(coroutineDispatchers.main) { val response = fileUploader.uploadFromUri(newAvatarUri, fileName, MimeTypes.Jpeg) setAvatarUrlTask.execute(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri)) userStore.updateAvatar(userId, response.contentUri) } } - override fun getAvatarUrl(userId: String, matrixCallback: MatrixCallback>): Cancelable { + override suspend fun getAvatarUrl(userId: String): Optional { val params = GetProfileInfoTask.Params(userId) - return getProfileInfoTask - .configureWith(params) { - this.callback = object : MatrixCallback { - override fun onSuccess(data: JsonDict) { - val avatarUrl = data[ProfileService.AVATAR_URL_KEY] as? String - matrixCallback.onSuccess(Optional.from(avatarUrl)) - } - - override fun onFailure(failure: Throwable) { - matrixCallback.onFailure(failure) - } - } - } - .executeBy(taskExecutor) + val data = getProfileInfoTask.execute(params) + val avatarUrl = data[ProfileService.AVATAR_URL_KEY] as? String + return Optional.from(avatarUrl) } - override fun getProfile(userId: String, matrixCallback: MatrixCallback): Cancelable { + override suspend fun getProfile(userId: String): JsonDict { val params = GetProfileInfoTask.Params(userId) - return getProfileInfoTask - .configureWith(params) { - this.callback = matrixCallback - } - .executeBy(taskExecutor) + return getProfileInfoTask.execute(params) } override fun getThreePids(): List { @@ -154,70 +126,38 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto ) } - override fun addThreePid(threePid: ThreePid, matrixCallback: MatrixCallback): Cancelable { - return addThreePidTask - .configureWith(AddThreePidTask.Params(threePid)) { - callback = matrixCallback - } - .executeBy(taskExecutor) + override suspend fun addThreePid(threePid: ThreePid) { + addThreePidTask.execute(AddThreePidTask.Params(threePid)) } - override fun submitSmsCode(threePid: ThreePid.Msisdn, code: String, matrixCallback: MatrixCallback): Cancelable { - return validateSmsCodeTask - .configureWith(ValidateSmsCodeTask.Params(threePid, code)) { - callback = matrixCallback - } - .executeBy(taskExecutor) + override suspend fun submitSmsCode(threePid: ThreePid.Msisdn, code: String) { + validateSmsCodeTask.execute(ValidateSmsCodeTask.Params(threePid, code)) } - override fun finalizeAddingThreePid(threePid: ThreePid, - userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, - matrixCallback: MatrixCallback): Cancelable { - return finalizeAddingThreePidTask - .configureWith(FinalizeAddingThreePidTask.Params( + override suspend fun finalizeAddingThreePid(threePid: ThreePid, + userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) { + finalizeAddingThreePidTask + .execute(FinalizeAddingThreePidTask.Params( threePid = threePid, userInteractiveAuthInterceptor = userInteractiveAuthInterceptor, userWantsToCancel = false - )) { - callback = alsoRefresh(matrixCallback) - } - .executeBy(taskExecutor) + )) + refreshThreePids() } - override fun cancelAddingThreePid(threePid: ThreePid, matrixCallback: MatrixCallback): Cancelable { - return finalizeAddingThreePidTask - .configureWith(FinalizeAddingThreePidTask.Params( + override suspend fun cancelAddingThreePid(threePid: ThreePid) { + finalizeAddingThreePidTask + .execute(FinalizeAddingThreePidTask.Params( threePid = threePid, userInteractiveAuthInterceptor = null, userWantsToCancel = true - )) { - callback = alsoRefresh(matrixCallback) - } - .executeBy(taskExecutor) + )) + refreshThreePids() } - /** - * Wrap the callback to fetch 3Pids from the server in case of success - */ - private fun alsoRefresh(callback: MatrixCallback): MatrixCallback { - return object : MatrixCallback { - override fun onFailure(failure: Throwable) { - callback.onFailure(failure) - } - - override fun onSuccess(data: Unit) { - refreshThreePids() - callback.onSuccess(data) - } - } - } - - override fun deleteThreePid(threePid: ThreePid, matrixCallback: MatrixCallback): Cancelable { - return deleteThreePidTask - .configureWith(DeleteThreePidTask.Params(threePid)) { - callback = alsoRefresh(matrixCallback) - } - .executeBy(taskExecutor) + override suspend fun deleteThreePid(threePid: ThreePid) { + deleteThreePidTask.execute(DeleteThreePidTask.Params(threePid)) + refreshThreePids() } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index af3d5461ef..2f099e7284 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -965,8 +965,8 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleChangeDisplayNameSlashCommand(changeDisplayName: ParsedCommand.ChangeDisplayName) { - launchSlashCommandFlow { - session.setDisplayName(session.myUserId, changeDisplayName.displayName, it) + launchSlashCommandFlowSuspendable { + session.setDisplayName(session.myUserId, changeDisplayName.displayName) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt index b66a37b75f..334464e304 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsGeneralFragment.kt @@ -55,7 +55,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.failure.isInvalidPassword import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService @@ -305,17 +304,13 @@ class VectorSettingsGeneralFragment @Inject constructor( private fun uploadAvatar(uri: Uri) { displayLoadingView() - session.updateAvatar(session.myUserId, uri, getFilenameFromUri(context, uri) ?: UUID.randomUUID().toString(), object : MatrixCallback { - override fun onSuccess(data: Unit) { - if (!isAdded) return - onCommonDone(null) + lifecycleScope.launch { + val result = runCatching { + session.updateAvatar(session.myUserId, uri, getFilenameFromUri(context, uri) ?: UUID.randomUUID().toString()) } - - override fun onFailure(failure: Throwable) { - if (!isAdded) return - onCommonDone(failure.localizedMessage) - } - }) + if (!isAdded) return@launch + onCommonDone(result.fold({ null }, { it.localizedMessage })) + } } // ============================================================================================================== @@ -477,20 +472,21 @@ class VectorSettingsGeneralFragment @Inject constructor( if (currentDisplayName != value) { displayLoadingView() - session.setDisplayName(session.myUserId, value, object : MatrixCallback { - override fun onSuccess(data: Unit) { - if (!isAdded) return - // refresh the settings value - mDisplayNamePreference.summary = value - mDisplayNamePreference.text = value - onCommonDone(null) - } - - override fun onFailure(failure: Throwable) { - if (!isAdded) return - onCommonDone(failure.localizedMessage) - } - }) + lifecycleScope.launch { + val result = runCatching { session.setDisplayName(session.myUserId, value) } + if (!isAdded) return@launch + result.fold( + { + // refresh the settings value + mDisplayNamePreference.summary = value + mDisplayNamePreference.text = value + onCommonDone(null) + }, + { + onCommonDone(it.localizedMessage) + } + ) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt index 89d632b813..ac565e72a1 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsViewModel.kt @@ -33,7 +33,6 @@ import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.ReadOnceTrue import im.vector.app.features.auth.ReAuthActivity import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.session.Session @@ -58,16 +57,18 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( private var pendingThreePid: ThreePid? = null // private var pendingSession: String? = null - private val loadingCallback: MatrixCallback = object : MatrixCallback { - override fun onFailure(failure: Throwable) { - isLoading(false) - _viewEvents.post(ThreePidsSettingsViewEvents.Failure(failure)) - } - - override fun onSuccess(data: Unit) { - pendingThreePid = null - isLoading(false) - } + private suspend fun loadingSuspendable(block: suspend () -> Unit) { + runCatching { block() } + .fold( + { + pendingThreePid = null + isLoading(false) + }, + { + isLoading(false) + _viewEvents.post(ThreePidsSettingsViewEvents.Failure(it)) + } + ) } private fun isLoading(isLoading: Boolean) { @@ -186,24 +187,23 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( viewModelScope.launch { // First submit the code - session.submitSmsCode(action.threePid, action.code, object : MatrixCallback { - override fun onSuccess(data: Unit) { - // then finalize - pendingThreePid = action.threePid - session.finalizeAddingThreePid(action.threePid, uiaInterceptor, loadingCallback) + try { + session.submitSmsCode(action.threePid, action.code) + } catch (failure: Throwable) { + isLoading(false) + setState { + copy( + msisdnValidationRequests = msisdnValidationRequests.toMutableMap().apply { + put(action.threePid.value, Fail(failure)) + } + ) } + return@launch + } - override fun onFailure(failure: Throwable) { - isLoading(false) - setState { - copy( - msisdnValidationRequests = msisdnValidationRequests.toMutableMap().apply { - put(action.threePid.value, Fail(failure)) - } - ) - } - } - }) + // then finalize + pendingThreePid = action.threePid + loadingSuspendable { session.finalizeAddingThreePid(action.threePid, uiaInterceptor) } } } @@ -230,21 +230,15 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( )))) } else { viewModelScope.launch { - session.addThreePid(action.threePid, object : MatrixCallback { - override fun onSuccess(data: Unit) { - // Also reset the state - setState { - copy( - uiState = ThreePidsSettingsUiState.Idle - ) - } - loadingCallback.onSuccess(data) + loadingSuspendable { + session.addThreePid(action.threePid) + // Also reset the state + setState { + copy( + uiState = ThreePidsSettingsUiState.Idle + ) } - - override fun onFailure(failure: Throwable) { - loadingCallback.onFailure(failure) - } - }) + } } } } @@ -254,14 +248,14 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( isLoading(true) pendingThreePid = action.threePid viewModelScope.launch { - session.finalizeAddingThreePid(action.threePid, uiaInterceptor, loadingCallback) + loadingSuspendable { session.finalizeAddingThreePid(action.threePid, uiaInterceptor) } } } private fun handleCancelThreePid(action: ThreePidsSettingsAction.CancelThreePid) { isLoading(true) viewModelScope.launch { - session.cancelAddingThreePid(action.threePid, loadingCallback) + loadingSuspendable { session.cancelAddingThreePid(action.threePid) } } } @@ -277,7 +271,7 @@ class ThreePidsSettingsViewModel @AssistedInject constructor( private fun handleDeleteThreePid(action: ThreePidsSettingsAction.DeleteThreePid) { isLoading(true) viewModelScope.launch { - session.deleteThreePid(action.threePid, loadingCallback) + loadingSuspendable { session.deleteThreePid(action.threePid) } } } } From 42166c1c0f8070e16f69a6a5b98f1807d6ed2fde Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Mon, 29 Mar 2021 19:48:26 +0100 Subject: [PATCH 122/249] Error handling Signed-off-by: Dominic Fischer --- .../home/room/detail/RoomDetailViewModel.kt | 15 ++++++++++++--- .../NotificationBroadcastReceiver.kt | 5 ++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 120e80a1b6..81968ab13a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -182,7 +182,10 @@ class RoomDetailViewModel @AssistedInject constructor( updateShowDialerOptionState() room.getRoomSummaryLive() viewModelScope.launch { - room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) + try { + room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) + } catch (_: Exception) { + } } // Inform the SDK that the room is displayed session.onRoomDisplayed(initialState.roomId) @@ -549,7 +552,10 @@ class RoomDetailViewModel @AssistedInject constructor( if (trackUnreadMessages.getAndSet(false)) { mostRecentDisplayedEvent?.root?.eventId?.also { viewModelScope.launch { - room.setReadMarker(it) + try { + room.setReadMarker(it) + } catch (_: Exception) { + } } } mostRecentDisplayedEvent = null @@ -1262,7 +1268,10 @@ class RoomDetailViewModel @AssistedInject constructor( private fun handleMarkAllAsRead() { viewModelScope.launch { - room.markAsRead(ReadService.MarkAsReadParams.BOTH) + try { + room.markAsRead(ReadService.MarkAsReadParams.BOTH) + } catch (_: Exception) { + } } } diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt index c4d7376c55..9dd41e4cea 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt @@ -93,7 +93,10 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { val room = session.getRoom(roomId) if (room != null) { GlobalScope.launch { - room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) + try { + room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) + } catch (_: Exception) { + } } } } From 1e58767374502cd721b7fd3f0e3b8fe15131fd57 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Mon, 29 Mar 2021 20:02:03 +0100 Subject: [PATCH 123/249] Missing file Signed-off-by: Dominic Fischer --- .../troubleshoot/TestPushFromPushGateway.kt | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt index da93d54075..6cf7d68561 100644 --- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt @@ -24,9 +24,8 @@ import im.vector.app.core.pushers.PushersManager import im.vector.app.core.resources.StringProvider import im.vector.app.features.settings.troubleshoot.TroubleshootTest import im.vector.app.push.fcm.FcmHelper -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.pushers.PushGatewayFailure -import org.matrix.android.sdk.api.util.Cancelable +import kotlinx.coroutines.* import javax.inject.Inject /** @@ -38,29 +37,31 @@ class TestPushFromPushGateway @Inject constructor(private val context: AppCompat private val pushersManager: PushersManager) : TroubleshootTest(R.string.settings_troubleshoot_test_push_loop_title) { - private var action: Cancelable? = null + private var action: Job? = null override fun perform(activityResultLauncher: ActivityResultLauncher) { val fcmToken = FcmHelper.getFcmToken(context) ?: run { status = TestStatus.FAILED return } - action = pushersManager.testPush(fcmToken, object : MatrixCallback { - override fun onFailure(failure: Throwable) { - description = if (failure is PushGatewayFailure.PusherRejected) { - stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_failed) - } else { - errorFormatter.toHumanReadable(failure) - } - status = TestStatus.FAILED - } - - override fun onSuccess(data: Unit) { - // Wait for the push to be received - description = stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_waiting_for_push) - status = TestStatus.RUNNING - } - }) + action = GlobalScope.launch { + runCatching { pushersManager.testPush(fcmToken) } + .fold( + { + // Wait for the push to be received + description = stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_waiting_for_push) + status = TestStatus.RUNNING + }, + { + description = if (failure is PushGatewayFailure.PusherRejected) { + stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_failed) + } else { + errorFormatter.toHumanReadable(it) + } + status = TestStatus.FAILED + } + ) + } } override fun onPushReceived() { @@ -69,6 +70,6 @@ class TestPushFromPushGateway @Inject constructor(private val context: AppCompat } override fun cancel() { - action?.cancel() + job?.cancel() } } From bc5e090b09734b440570861d9a5358c7fa0b1110 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Mon, 29 Mar 2021 20:06:28 +0100 Subject: [PATCH 124/249] Lint Signed-off-by: Dominic Fischer --- .../app/features/crypto/recover/BootstrapCrossSigningTask.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt index 87074b7a12..70cda4bf79 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/recover/BootstrapCrossSigningTask.kt @@ -192,7 +192,7 @@ class BootstrapCrossSigningTask @Inject constructor( ssssService.storeSecret( USER_SIGNING_KEY_SSSS_NAME, uskPrivateKey, - listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)), + listOf(SharedSecretStorageService.KeyRef(keyInfo.keyId, keyInfo.keySpec)) ) params.progressListener?.onProgress( WaitingViewData( From bc68075ae3d698c4cc8f5828acbe1038f956e7d3 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sun, 28 Mar 2021 12:52:12 +0100 Subject: [PATCH 125/249] Convert MembershipService to suspend functions Signed-off-by: Dominic Fischer --- .../java/org/matrix/android/sdk/rx/RxRoom.kt | 17 ++--- .../android/sdk/common/CryptoTestHelper.kt | 10 +-- .../crypto/gossiping/KeyShareTests.kt | 4 +- .../session/room/members/MembershipService.kt | 31 +++------ .../membership/DefaultMembershipService.kt | 69 +++++-------------- .../home/room/detail/RoomDetailViewModel.kt | 34 +++++---- .../home/room/list/RoomListViewModel.kt | 40 +++++------ .../NotificationBroadcastReceiver.kt | 18 +++-- .../RoomMemberProfileViewModel.kt | 19 ++--- .../roomprofile/RoomProfileViewModel.kt | 12 ++-- .../banned/RoomBannedMemberListViewModel.kt | 5 +- .../features/widgets/WidgetPostAPIHandler.kt | 4 +- 12 files changed, 109 insertions(+), 154 deletions(-) diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt index b938f60e39..21db4e1893 100644 --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt @@ -21,6 +21,7 @@ import io.reactivex.Completable import io.reactivex.Observable import io.reactivex.Single import kotlinx.coroutines.rx2.rxCompletable +import kotlinx.coroutines.rx2.rxSingle import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.identity.ThreePid @@ -90,13 +91,13 @@ class RxRoom(private val room: Room) { return room.getMyReadReceiptLive().asObservable() } - fun loadRoomMembersIfNeeded(): Single = singleBuilder { - room.loadRoomMembersIfNeeded(it) + fun loadRoomMembersIfNeeded(): Single = rxSingle { + room.loadRoomMembersIfNeeded() } fun joinRoom(reason: String? = null, - viaServers: List = emptyList()): Single = singleBuilder { - room.join(reason, viaServers, it) + viaServers: List = emptyList()): Single = rxSingle { + room.join(reason, viaServers) } fun liveEventReadReceipts(eventId: String): Observable> { @@ -114,12 +115,12 @@ class RxRoom(private val room: Room) { return room.getLiveRoomNotificationState().asObservable() } - fun invite(userId: String, reason: String? = null): Completable = completableBuilder { - room.invite(userId, reason, it) + fun invite(userId: String, reason: String? = null): Completable = rxCompletable { + room.invite(userId, reason) } - fun invite3pid(threePid: ThreePid): Completable = completableBuilder { - room.invite3pid(threePid, it) + fun invite3pid(threePid: ThreePid): Completable = rxCompletable { + room.invite3pid(threePid) } fun updateTopic(topic: String): Completable = rxCompletable { diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt index eb7e4a9fbe..effea8313a 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt @@ -112,8 +112,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { bobRoomSummariesLive.observeForever(newRoomObserver) } - mTestHelper.doSync { - aliceRoom.invite(bobSession.myUserId, callback = it) + mTestHelper.runBlockingTest { + aliceRoom.invite(bobSession.myUserId) } mTestHelper.await(lock1) @@ -172,8 +172,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { fun createSamAccountAndInviteToTheRoom(room: Room): Session { val samSession = mTestHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams) - mTestHelper.doSync { - room.invite(samSession.myUserId, null, it) + mTestHelper.runBlockingTest { + room.invite(samSession.myUserId, null) } mTestHelper.doSync { @@ -411,7 +411,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { val sessions = mutableListOf(aliceSession) for (index in 1 until numberOfMembers) { val session = mTestHelper.createAccount("User_$index", defaultSessionParams) - mTestHelper.doSync(timeout = 600_000) { room.invite(session.myUserId, null, it) } + mTestHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) } println("TEST -> " + session.myUserId + " invited") mTestHelper.doSync { session.joinRoom(room.roomId, null, emptyList(), it) } println("TEST -> " + session.myUserId + " joined") diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt index 8c3917adc1..e6b364f3fb 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt @@ -367,8 +367,8 @@ class KeyShareTests : InstrumentedTest { } // Let alice invite bob - mTestHelper.doSync { - roomAlicePov.invite(bobSession.myUserId, null, it) + mTestHelper.runBlockingTest { + roomAlicePov.invite(bobSession.myUserId, null) } mTestHelper.doSync { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt index 2c3ffac687..198d6677a0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt @@ -17,10 +17,8 @@ package org.matrix.android.sdk.api.session.room.members import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary -import org.matrix.android.sdk.api.util.Cancelable /** * This interface defines methods to handling membership. It's implemented at the room level. @@ -29,9 +27,8 @@ interface MembershipService { /** * This methods load all room members if it was done yet. - * @return a [Cancelable] */ - fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback): Cancelable + suspend fun loadRoomMembersIfNeeded() /** * Return the roomMember with userId or null. @@ -60,47 +57,35 @@ interface MembershipService { /** * Invite a user in the room */ - fun invite(userId: String, - reason: String? = null, - callback: MatrixCallback): Cancelable + suspend fun invite(userId: String, reason: String? = null) /** * Invite a user with email or phone number in the room */ - fun invite3pid(threePid: ThreePid, - callback: MatrixCallback): Cancelable + suspend fun invite3pid(threePid: ThreePid) /** * Ban a user from the room */ - fun ban(userId: String, - reason: String? = null, - callback: MatrixCallback): Cancelable + suspend fun ban(userId: String, reason: String? = null) /** * Unban a user from the room */ - fun unban(userId: String, - reason: String? = null, - callback: MatrixCallback): Cancelable + suspend fun unban(userId: String, reason: String? = null) /** * Kick a user from the room */ - fun kick(userId: String, - reason: String? = null, - callback: MatrixCallback): Cancelable + suspend fun kick(userId: String, reason: String? = null) /** * Join the room, or accept an invitation. */ - fun join(reason: String? = null, - viaServers: List = emptyList(), - callback: MatrixCallback): Cancelable + suspend fun join(reason: String? = null, viaServers: List = emptyList()) /** * Leave the room, or reject an invitation. */ - fun leave(reason: String? = null, - callback: MatrixCallback): Cancelable + suspend fun leave(reason: String? = null) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt index cd1c9bbbdd..41e891f78e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt @@ -21,13 +21,11 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.members.MembershipService import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields @@ -39,8 +37,6 @@ import org.matrix.android.sdk.internal.session.room.membership.joining.InviteTas import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask import org.matrix.android.sdk.internal.session.room.membership.leaving.LeaveRoomTask import org.matrix.android.sdk.internal.session.room.membership.threepid.InviteThreePidTask -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.fetchCopied import io.realm.Realm import io.realm.RealmQuery @@ -48,7 +44,6 @@ import io.realm.RealmQuery internal class DefaultMembershipService @AssistedInject constructor( @Assisted private val roomId: String, @SessionDatabase private val monarchy: Monarchy, - private val taskExecutor: TaskExecutor, private val loadRoomMembersTask: LoadRoomMembersTask, private val inviteTask: InviteTask, private val inviteThreePidTask: InviteThreePidTask, @@ -64,13 +59,9 @@ internal class DefaultMembershipService @AssistedInject constructor( fun create(roomId: String): DefaultMembershipService } - override fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback): Cancelable { + override suspend fun loadRoomMembersIfNeeded() { val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE) - return loadRoomMembersTask - .configureWith(params) { - this.callback = matrixCallback - } - .executeBy(taskExecutor) + loadRoomMembersTask.execute(params) } override fun getRoomMember(userId: String): RoomMemberSummary? { @@ -120,66 +111,38 @@ internal class DefaultMembershipService @AssistedInject constructor( } } - override fun ban(userId: String, reason: String?, callback: MatrixCallback): Cancelable { + override suspend fun ban(userId: String, reason: String?) { val params = MembershipAdminTask.Params(MembershipAdminTask.Type.BAN, roomId, userId, reason) - return membershipAdminTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + membershipAdminTask.execute(params) } - override fun unban(userId: String, reason: String?, callback: MatrixCallback): Cancelable { + override suspend fun unban(userId: String, reason: String?) { val params = MembershipAdminTask.Params(MembershipAdminTask.Type.UNBAN, roomId, userId, reason) - return membershipAdminTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + membershipAdminTask.execute(params) } - override fun kick(userId: String, reason: String?, callback: MatrixCallback): Cancelable { + override suspend fun kick(userId: String, reason: String?) { val params = MembershipAdminTask.Params(MembershipAdminTask.Type.KICK, roomId, userId, reason) - return membershipAdminTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + membershipAdminTask.execute(params) } - override fun invite(userId: String, reason: String?, callback: MatrixCallback): Cancelable { + override suspend fun invite(userId: String, reason: String?) { val params = InviteTask.Params(roomId, userId, reason) - return inviteTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + inviteTask.execute(params) } - override fun invite3pid(threePid: ThreePid, callback: MatrixCallback): Cancelable { + override suspend fun invite3pid(threePid: ThreePid) { val params = InviteThreePidTask.Params(roomId, threePid) - return inviteThreePidTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + return inviteThreePidTask.execute(params) } - override fun join(reason: String?, viaServers: List, callback: MatrixCallback): Cancelable { + override suspend fun join(reason: String?, viaServers: List) { val params = JoinRoomTask.Params(roomId, reason, viaServers) - return joinTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + joinTask.execute(params) } - override fun leave(reason: String?, callback: MatrixCallback): Cancelable { + override suspend fun leave(reason: String?) { val params = LeaveRoomTask.Params(roomId, reason) - return leaveRoomTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + leaveRoomTask.execute(params) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index af3d5461ef..0584641c59 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -940,14 +940,14 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleInviteSlashCommand(invite: ParsedCommand.Invite) { - launchSlashCommandFlow { - room.invite(invite.userId, invite.reason, it) + launchSlashCommandFlowSuspendable { + room.invite(invite.userId, invite.reason) } } private fun handleInvite3pidSlashCommand(invite: ParsedCommand.Invite3Pid) { - launchSlashCommandFlow { - room.invite3pid(invite.threePid, it) + launchSlashCommandFlowSuspendable { + room.invite3pid(invite.threePid) } } @@ -971,20 +971,20 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleKickSlashCommand(kick: ParsedCommand.KickUser) { - launchSlashCommandFlow { - room.kick(kick.userId, kick.reason, it) + launchSlashCommandFlowSuspendable { + room.kick(kick.userId, kick.reason) } } private fun handleBanSlashCommand(ban: ParsedCommand.BanUser) { - launchSlashCommandFlow { - room.ban(ban.userId, ban.reason, it) + launchSlashCommandFlowSuspendable { + room.ban(ban.userId, ban.reason) } } private fun handleUnbanSlashCommand(unban: ParsedCommand.UnbanUser) { - launchSlashCommandFlow { - room.unban(unban.userId, unban.reason, it) + launchSlashCommandFlowSuspendable { + room.unban(unban.userId, unban.reason) } } @@ -1088,11 +1088,21 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleRejectInvite() { - room.leave(null, NoOpMatrixCallback()) + viewModelScope.launch { + try { + room.leave(null) + } catch (_: Exception) { + } + } } private fun handleAcceptInvite() { - room.join(callback = NoOpMatrixCallback()) + viewModelScope.launch { + try { + room.join() + } catch (_: Exception) { + } + } } private fun handleEditAction(action: RoomDetailAction.EnterEditMode) { 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 6e5081a31c..3a5e797f98 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 @@ -26,7 +26,6 @@ import im.vector.app.core.utils.DataSource import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session @@ -127,17 +126,17 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, return@withState } - session.getRoom(roomId)?.join(callback = object : MatrixCallback { - override fun onSuccess(data: Unit) { + val room = session.getRoom(roomId) ?: return@withState + viewModelScope.launch { + try { + room.join() // We do not update the joiningRoomsIds here, because, the room is not joined yet regarding the sync data. // Instead, we wait for the room to be joined - } - - override fun onFailure(failure: Throwable) { + } catch (failure: Throwable) { // Notify the user _viewEvents.post(RoomListViewEvents.Failure(failure)) } - }) + } } private fun handleRejectInvitation(action: RoomListAction.RejectInvitation) = withState { state -> @@ -149,19 +148,19 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, return@withState } - session.getRoom(roomId)?.leave(null, object : MatrixCallback { - override fun onSuccess(data: Unit) { + val room = session.getRoom(roomId) ?: return@withState + viewModelScope.launch { + try { + room.leave(null) // We do not update the rejectingRoomsIds here, because, the room is not rejected yet regarding the sync data. // Instead, we wait for the room to be rejected // Known bug: if the user is invited again (after rejecting the first invitation), the loading will be displayed instead of the buttons. // If we update the state, the button will be displayed again, so it's not ideal... - } - - override fun onFailure(failure: Throwable) { + } catch (failure: Throwable) { // Notify the user _viewEvents.post(RoomListViewEvents.Failure(failure)) } - }) + } } private fun handleMarkAllRoomsRead() = withState { state -> @@ -220,15 +219,12 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, private fun handleLeaveRoom(action: RoomListAction.LeaveRoom) { _viewEvents.post(RoomListViewEvents.Loading(null)) - session.getRoom(action.roomId)?.leave(null, object : MatrixCallback { - override fun onSuccess(data: Unit) { - _viewEvents.post(RoomListViewEvents.Done) - } - - override fun onFailure(failure: Throwable) { - _viewEvents.post(RoomListViewEvents.Failure(failure)) - } - }) + val room = session.getRoom(action.roomId) ?: return + viewModelScope.launch { + val value = runCatching { room.leave(null) } + .fold({ RoomListViewEvents.Done }, { RoomListViewEvents.Failure(it) }) + _viewEvents.post(value) + } } private fun observeMembershipChanges() { diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt index d79d16a052..8136a172fb 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt @@ -23,6 +23,8 @@ import androidx.core.app.RemoteInput import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.vectorComponent +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.Room @@ -74,15 +76,23 @@ class NotificationBroadcastReceiver : BroadcastReceiver() { private fun handleJoinRoom(roomId: String) { activeSessionHolder.getSafeActiveSession()?.let { session -> - session.getRoom(roomId) - ?.join(callback = NoOpMatrixCallback()) + val room = session.getRoom(roomId) + if (room != null) { + GlobalScope.launch { + room.join() + } + } } } private fun handleRejectRoom(roomId: String) { activeSessionHolder.getSafeActiveSession()?.let { session -> - session.getRoom(roomId) - ?.leave(callback = NoOpMatrixCallback()) + val room = session.getRoom(roomId) + if (room != null) { + GlobalScope.launch { + room.leave() + } + } } } diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt index 0556b9d2d6..e34e48e09c 100644 --- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileViewModel.kt @@ -53,7 +53,6 @@ import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.api.util.toOptional -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap @@ -198,9 +197,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v viewModelScope.launch { try { _viewEvents.post(RoomMemberProfileViewEvents.Loading()) - awaitCallback { - room.invite(initialState.userId, callback = it) - } + room.invite(initialState.userId) _viewEvents.post(RoomMemberProfileViewEvents.OnInviteActionSuccess) } catch (failure: Throwable) { _viewEvents.post(RoomMemberProfileViewEvents.Failure(failure)) @@ -215,9 +212,7 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v viewModelScope.launch { try { _viewEvents.post(RoomMemberProfileViewEvents.Loading()) - awaitCallback { - room.kick(initialState.userId, action.reason, it) - } + room.kick(initialState.userId, action.reason) _viewEvents.post(RoomMemberProfileViewEvents.OnKickActionSuccess) } catch (failure: Throwable) { _viewEvents.post(RoomMemberProfileViewEvents.Failure(failure)) @@ -233,12 +228,10 @@ class RoomMemberProfileViewModel @AssistedInject constructor(@Assisted private v viewModelScope.launch { try { _viewEvents.post(RoomMemberProfileViewEvents.Loading()) - awaitCallback { - if (membership == Membership.BAN) { - room.unban(initialState.userId, action.reason, it) - } else { - room.ban(initialState.userId, action.reason, it) - } + if (membership == Membership.BAN) { + room.unban(initialState.userId, action.reason) + } else { + room.ban(initialState.userId, action.reason) } _viewEvents.post(RoomMemberProfileViewEvents.OnBanActionSuccess) } catch (failure: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt index c8bb6b5b5c..209ebcc35b 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileViewModel.kt @@ -32,7 +32,6 @@ import im.vector.app.features.home.ShortcutCreator import im.vector.app.features.powerlevel.PowerLevelsObservableFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.EventType @@ -169,15 +168,14 @@ class RoomProfileViewModel @AssistedInject constructor( private fun handleLeaveRoom() { _viewEvents.post(RoomProfileViewEvents.Loading(stringProvider.getString(R.string.room_profile_leaving_room))) - room.leave(null, object : MatrixCallback { - override fun onSuccess(data: Unit) { + viewModelScope.launch { + try { + room.leave(null) // Do nothing, we will be closing the room automatically when it will get back from sync - } - - override fun onFailure(failure: Throwable) { + } catch (failure: Throwable) { _viewEvents.post(RoomProfileViewEvents.Failure(failure)) } - }) + } } private fun handleShareRoomProfile() { diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt index 5663392c6c..9e12e30399 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListViewModel.kt @@ -39,7 +39,6 @@ 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.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx import org.matrix.android.sdk.rx.unwrap @@ -124,9 +123,7 @@ class RoomBannedMemberListViewModel @AssistedInject constructor(@Assisted initia } viewModelScope.launch(Dispatchers.IO) { try { - awaitCallback { - room.unban(roomMemberSummary.userId, null, it) - } + room.unban(roomMemberSummary.userId, null) } catch (failure: Throwable) { _viewEvents.post(RoomBannedMemberListViewEvents.ToastError(stringProvider.getString(R.string.failed_to_unban))) } finally { diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt index 13d49eb20b..467271c12d 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt @@ -386,7 +386,9 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo if (member != null && member.membership == Membership.JOIN) { widgetPostAPIMediator.sendSuccess(eventData) } else { - room.invite(userId = userId, callback = createWidgetAPICallback(widgetPostAPIMediator, eventData)) + launchWidgetAPIAction(widgetPostAPIMediator, eventData) { + room.invite(userId = userId) + } } } From 501b870c351d9ef7eb3de70f08d44aaec2affbe0 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Sat, 27 Mar 2021 18:41:29 +0000 Subject: [PATCH 126/249] Convert IdentityService to suspend functions Signed-off-by: Dominic Fischer --- .../api/session/identity/IdentityService.kt | 29 ++-- .../identity/DefaultIdentityService.kt | 149 +++++++----------- .../contactsbook/ContactsBookViewModel.kt | 78 +++++---- .../discovery/DiscoverySettingsViewModel.kt | 25 ++- .../change/SetIdentityServerViewModel.kt | 5 +- 5 files changed, 119 insertions(+), 167 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt index aedb813735..8f8967e8fb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt @@ -16,9 +16,6 @@ package org.matrix.android.sdk.api.session.identity -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.util.Cancelable - /** * Provides access to the identity server configuration and services identity server can provide */ @@ -40,55 +37,55 @@ interface IdentityService { * See https://matrix.org/docs/spec/identity_service/latest#status-check * RiotX SDK only supports identity server API v2 */ - fun isValidIdentityServer(url: String, callback: MatrixCallback): Cancelable + suspend fun isValidIdentityServer(url: String) /** * Update the identity server url. * If successful, any previous identity server will be disconnected. * In case of error, any previous identity server will remain configured. * @param url the new url. - * @param callback will notify the user if change is successful. The String will be the final url of the identity server. + * @return The String will be the final url of the identity server. * The SDK can prepend "https://" for instance. */ - fun setNewIdentityServer(url: String, callback: MatrixCallback): Cancelable + suspend fun setNewIdentityServer(url: String): String /** * Disconnect (logout) from the current identity server */ - fun disconnect(callback: MatrixCallback): Cancelable + suspend fun disconnect() /** * This will ask the identity server to send an email or an SMS to let the user confirm he owns the ThreePid */ - fun startBindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable + suspend fun startBindThreePid(threePid: ThreePid) /** * This will cancel a pending binding of threePid. */ - fun cancelBindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable + suspend fun cancelBindThreePid(threePid: ThreePid) /** * This will ask the identity server to send an new email or a new SMS to let the user confirm he owns the ThreePid */ - fun sendAgainValidationCode(threePid: ThreePid, callback: MatrixCallback): Cancelable + suspend fun sendAgainValidationCode(threePid: ThreePid) /** * Submit the code that the identity server has sent to the user (in email or SMS) * Once successful, you will have to call [finalizeBindThreePid] * @param code the code sent to the user */ - fun submitValidationToken(threePid: ThreePid, code: String, callback: MatrixCallback): Cancelable + suspend fun submitValidationToken(threePid: ThreePid, code: String) /** * This will perform the actual association of ThreePid and Matrix account */ - fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable + suspend fun finalizeBindThreePid(threePid: ThreePid) /** * Unbind a threePid * The request will actually be done on the homeserver */ - fun unbindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable + suspend fun unbindThreePid(threePid: ThreePid) /** * Search MatrixId of users providing email and phone numbers @@ -96,7 +93,7 @@ interface IdentityService { * Application has to explicitly ask for the user consent, and the answer can be stored using [setUserConsent] * Please see https://support.google.com/googleplay/android-developer/answer/9888076?hl=en for more details. */ - fun lookUp(threePids: List, callback: MatrixCallback>): Cancelable + suspend fun lookUp(threePids: List): List /** * Return the current user consent for the current identity server, which has been stored using [setUserConsent]. @@ -120,9 +117,9 @@ interface IdentityService { * A lookup will be performed, but also pending binding state will be restored * * @param threePids the list of threePid the user owns (retrieved form the homeserver) - * @param callback onSuccess will be called with a map of ThreePid -> SharedState + * @return a map of ThreePid -> SharedState */ - fun getShareStatus(threePids: List, callback: MatrixCallback>): Cancelable + suspend fun getShareStatus(threePids: List): Map fun addListener(listener: IdentityServiceListener) fun removeListener(listener: IdentityServiceListener) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt index 948e387cb1..f5391d6cdb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt @@ -20,7 +20,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import dagger.Lazy -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.Failure @@ -33,8 +32,6 @@ import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.IdentityServiceListener import org.matrix.android.sdk.api.session.identity.SharedState import org.matrix.android.sdk.api.session.identity.ThreePid -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.api.util.NoOpCancellable import org.matrix.android.sdk.internal.di.AuthenticatedIdentity import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate import org.matrix.android.sdk.internal.extensions.observeNotNull @@ -49,8 +46,6 @@ import org.matrix.android.sdk.internal.session.sync.model.accountdata.IdentitySe 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.task.TaskExecutor -import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.ensureProtocol import kotlinx.coroutines.withContext @@ -83,8 +78,7 @@ internal class DefaultIdentityService @Inject constructor( private val identityApiProvider: IdentityApiProvider, private val accountDataDataSource: AccountDataDataSource, private val homeServerCapabilitiesService: HomeServerCapabilitiesService, - private val sessionParams: SessionParams, - private val taskExecutor: TaskExecutor + private val sessionParams: SessionParams ) : IdentityService, SessionLifecycleObserver { private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry } @@ -136,101 +130,81 @@ internal class DefaultIdentityService @Inject constructor( return identityStore.getIdentityData()?.identityServerUrl } - override fun startBindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable { + override suspend fun startBindThreePid(threePid: ThreePid) { if (homeServerCapabilitiesService.getHomeServerCapabilities().lastVersionIdentityServerSupported.not()) { - callback.onFailure(IdentityServiceError.OutdatedHomeServer) - return NoOpCancellable + throw IdentityServiceError.OutdatedHomeServer } - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, false)) - } + identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, false)) } - override fun cancelBindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - identityStore.deletePendingBinding(threePid) - } + override suspend fun cancelBindThreePid(threePid: ThreePid) { + identityStore.deletePendingBinding(threePid) } - override fun sendAgainValidationCode(threePid: ThreePid, callback: MatrixCallback): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, true)) - } + override suspend fun sendAgainValidationCode(threePid: ThreePid) { + identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, true)) } - override fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable { + override suspend fun finalizeBindThreePid(threePid: ThreePid) { if (homeServerCapabilitiesService.getHomeServerCapabilities().lastVersionIdentityServerSupported.not()) { - callback.onFailure(IdentityServiceError.OutdatedHomeServer) - return NoOpCancellable + throw IdentityServiceError.OutdatedHomeServer } - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - bindThreePidsTask.execute(BindThreePidsTask.Params(threePid)) - } + bindThreePidsTask.execute(BindThreePidsTask.Params(threePid)) } - override fun submitValidationToken(threePid: ThreePid, code: String, callback: MatrixCallback): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - submitTokenForBindingTask.execute(IdentitySubmitTokenForBindingTask.Params(threePid, code)) - } + override suspend fun submitValidationToken(threePid: ThreePid, code: String) { + submitTokenForBindingTask.execute(IdentitySubmitTokenForBindingTask.Params(threePid, code)) } - override fun unbindThreePid(threePid: ThreePid, callback: MatrixCallback): Cancelable { + override suspend fun unbindThreePid(threePid: ThreePid) { if (homeServerCapabilitiesService.getHomeServerCapabilities().lastVersionIdentityServerSupported.not()) { - callback.onFailure(IdentityServiceError.OutdatedHomeServer) - return NoOpCancellable + throw IdentityServiceError.OutdatedHomeServer } - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - unbindThreePidsTask.execute(UnbindThreePidsTask.Params(threePid)) - } + unbindThreePidsTask.execute(UnbindThreePidsTask.Params(threePid)) } - override fun isValidIdentityServer(url: String, callback: MatrixCallback): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java) + override suspend fun isValidIdentityServer(url: String) { + val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java) - identityPingTask.execute(IdentityPingTask.Params(api)) - } + identityPingTask.execute(IdentityPingTask.Params(api)) } - override fun disconnect(callback: MatrixCallback): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - identityDisconnectTask.execute(Unit) + override suspend fun disconnect() { + identityDisconnectTask.execute(Unit) - identityStore.setUrl(null) - updateIdentityAPI(null) - updateAccountData(null) - } + identityStore.setUrl(null) + updateIdentityAPI(null) + updateAccountData(null) } - override fun setNewIdentityServer(url: String, callback: MatrixCallback): Cancelable { + override suspend fun setNewIdentityServer(url: String): String { val urlCandidate = url.ensureProtocol() - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - val current = getCurrentIdentityServerUrl() - if (urlCandidate == current) { - // Nothing to do - Timber.d("Same URL, nothing to do") - } else { - // Disconnect previous one if any, first, because the token will change. - // In case of error when configuring the new identity server, this is not a big deal, - // we will ask for a new token on the previous Identity server - runCatching { identityDisconnectTask.execute(Unit) } - .onFailure { Timber.w(it, "Unable to disconnect identity server") } + val current = getCurrentIdentityServerUrl() + if (urlCandidate == current) { + // Nothing to do + Timber.d("Same URL, nothing to do") + } else { + // Disconnect previous one if any, first, because the token will change. + // In case of error when configuring the new identity server, this is not a big deal, + // we will ask for a new token on the previous Identity server + runCatching { identityDisconnectTask.execute(Unit) } + .onFailure { Timber.w(it, "Unable to disconnect identity server") } - // Try to get a token - val token = getNewIdentityServerToken(urlCandidate) + // Try to get a token + val token = getNewIdentityServerToken(urlCandidate) - identityStore.setUrl(urlCandidate) - identityStore.setToken(token) - updateIdentityAPI(urlCandidate) + identityStore.setUrl(urlCandidate) + identityStore.setToken(token) + updateIdentityAPI(urlCandidate) - updateAccountData(urlCandidate) - } - urlCandidate + updateAccountData(urlCandidate) } + + return urlCandidate } private suspend fun updateAccountData(url: String?) { @@ -252,45 +226,38 @@ internal class DefaultIdentityService @Inject constructor( identityStore.setUserConsent(newValue) } - override fun lookUp(threePids: List, callback: MatrixCallback>): Cancelable { + override suspend fun lookUp(threePids: List): List { if (!getUserConsent()) { - callback.onFailure(IdentityServiceError.UserConsentNotProvided) - return NoOpCancellable + throw IdentityServiceError.UserConsentNotProvided } if (threePids.isEmpty()) { - callback.onSuccess(emptyList()) - return NoOpCancellable + return emptyList() } - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - lookUpInternal(true, threePids) - } + return lookUpInternal(true, threePids) } - override fun getShareStatus(threePids: List, callback: MatrixCallback>): Cancelable { + override suspend fun getShareStatus(threePids: List): Map { // Note: we do not require user consent here, because it is used for emails and phone numbers that the user has already sent // to the home server, and not emails and phone numbers from the contact book of the user if (threePids.isEmpty()) { - callback.onSuccess(emptyMap()) - return NoOpCancellable + return emptyMap() } - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - val lookupResult = lookUpInternal(true, threePids) + val lookupResult = lookUpInternal(true, threePids) - threePids.associateWith { threePid -> - // If not in lookup result, check if there is a pending binding - if (lookupResult.firstOrNull { it.threePid == threePid } == null) { - if (identityStore.getPendingBinding(threePid) == null) { - SharedState.NOT_SHARED - } else { - SharedState.BINDING_IN_PROGRESS - } + return threePids.associateWith { threePid -> + // If not in lookup result, check if there is a pending binding + if (lookupResult.firstOrNull { it.threePid == threePid } == null) { + if (identityStore.getPendingBinding(threePid) == null) { + SharedState.NOT_SHARED } else { - SharedState.SHARED + SharedState.BINDING_IN_PROGRESS } + } else { + SharedState.SHARED } } } diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt index 05af63d7ba..cfbdef8ffb 100644 --- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookViewModel.kt @@ -33,9 +33,7 @@ import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.identity.FoundThreePid import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.ThreePid import timber.log.Timber @@ -101,56 +99,56 @@ class ContactsBookViewModel @AssistedInject constructor(@Assisted } } - private fun performLookup(data: List) { + private fun performLookup(contacts: List) { if (!session.identityService().getUserConsent()) { return } viewModelScope.launch { - val threePids = data.flatMap { contact -> + val threePids = contacts.flatMap { contact -> contact.emails.map { ThreePid.Email(it.email) } + contact.msisdns.map { ThreePid.Msisdn(it.phoneNumber) } } - session.identityService().lookUp(threePids, object : MatrixCallback> { - override fun onFailure(failure: Throwable) { - Timber.w(failure, "Unable to perform the lookup") - // Should not happen, but just to be sure - if (failure is IdentityServiceError.UserConsentNotProvided) { - setState { - copy(userConsent = false) - } - } - } - - override fun onSuccess(data: List) { - mappedContacts = allContacts.map { contactModel -> - contactModel.copy( - emails = contactModel.emails.map { email -> - email.copy( - matrixId = data - .firstOrNull { foundThreePid -> foundThreePid.threePid.value == email.email } - ?.matrixId - ) - }, - msisdns = contactModel.msisdns.map { msisdn -> - msisdn.copy( - matrixId = data - .firstOrNull { foundThreePid -> foundThreePid.threePid.value == msisdn.phoneNumber } - ?.matrixId - ) - } - ) - } + val data = try { + session.identityService().lookUp(threePids) + } catch (failure: Throwable) { + Timber.w(failure, "Unable to perform the lookup") + // Should not happen, but just to be sure + if (failure is IdentityServiceError.UserConsentNotProvided) { setState { - copy( - isBoundRetrieved = true - ) + copy(userConsent = false) } - - updateFilteredMappedContacts() } - }) + return@launch + } + + mappedContacts = allContacts.map { contactModel -> + contactModel.copy( + emails = contactModel.emails.map { email -> + email.copy( + matrixId = data + .firstOrNull { foundThreePid -> foundThreePid.threePid.value == email.email } + ?.matrixId + ) + }, + msisdns = contactModel.msisdns.map { msisdn -> + msisdn.copy( + matrixId = data + .firstOrNull { foundThreePid -> foundThreePid.threePid.value == msisdn.phoneNumber } + ?.matrixId + ) + } + ) + } + + setState { + copy( + isBoundRetrieved = true + ) + } + + updateFilteredMappedContacts() } } diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt index bf2defafa1..11fd796534 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsViewModel.kt @@ -35,7 +35,6 @@ import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.IdentityServiceListener import org.matrix.android.sdk.api.session.identity.SharedState import org.matrix.android.sdk.api.session.identity.ThreePid -import org.matrix.android.sdk.internal.util.awaitCallback import org.matrix.android.sdk.rx.rx class DiscoverySettingsViewModel @AssistedInject constructor( @@ -123,7 +122,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor( viewModelScope.launch { try { - awaitCallback { session.identityService().disconnect(it) } + session.identityService().disconnect() setState { copy( identityServer = Success(null), @@ -141,9 +140,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor( viewModelScope.launch { try { - val data = awaitCallback { - session.identityService().setNewIdentityServer(action.url, it) - } + val data = session.identityService().setNewIdentityServer(action.url) setState { copy( identityServer = Success(data), @@ -163,7 +160,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor( viewModelScope.launch { try { - awaitCallback { identityService.startBindThreePid(action.threePid, it) } + identityService.startBindThreePid(action.threePid) changeThreePidState(action.threePid, Success(SharedState.BINDING_IN_PROGRESS)) } catch (failure: Throwable) { _viewEvents.post(DiscoverySettingsViewEvents.Failure(failure)) @@ -240,7 +237,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor( viewModelScope.launch { try { - awaitCallback { identityService.unbindThreePid(threePid, it) } + identityService.unbindThreePid(threePid) changeThreePidState(threePid, Success(SharedState.NOT_SHARED)) } catch (failure: Throwable) { _viewEvents.post(DiscoverySettingsViewEvents.Failure(failure)) @@ -256,7 +253,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor( viewModelScope.launch { try { - awaitCallback { identityService.unbindThreePid(threePid, it) } + identityService.unbindThreePid(threePid) changeThreePidState(threePid, Success(SharedState.NOT_SHARED)) } catch (failure: Throwable) { _viewEvents.post(DiscoverySettingsViewEvents.Failure(failure)) @@ -268,7 +265,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor( private fun cancelBinding(action: DiscoverySettingsAction.CancelBinding) { viewModelScope.launch { try { - awaitCallback { identityService.cancelBindThreePid(action.threePid, it) } + identityService.cancelBindThreePid(action.threePid) changeThreePidState(action.threePid, Success(SharedState.NOT_SHARED)) changeThreePidSubmitState(action.threePid, Uninitialized) } catch (failure: Throwable) { @@ -304,9 +301,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor( viewModelScope.launch { try { - val data = awaitCallback> { - identityService.getShareStatus(threePids, it) - } + val data = identityService.getShareStatus(threePids) setState { copy( emailList = Success(data.filter { it.key is ThreePid.Email }.toPidInfoList()), @@ -346,9 +341,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor( viewModelScope.launch { try { - awaitCallback { - identityService.submitValidationToken(action.threePid, action.code, it) - } + identityService.submitValidationToken(action.threePid, action.code) changeThreePidSubmitState(action.threePid, Uninitialized) finalizeBind3pid(DiscoverySettingsAction.FinalizeBind3pid(action.threePid), true) } catch (failure: Throwable) { @@ -371,7 +364,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor( viewModelScope.launch { try { - awaitCallback { identityService.finalizeBindThreePid(threePid, it) } + identityService.finalizeBindThreePid(threePid) changeThreePidSubmitState(action.threePid, Uninitialized) changeThreePidState(action.threePid, Success(SharedState.SHARED)) } catch (failure: Throwable) { diff --git a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt index 9455b1bff4..08632a2bd1 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/change/SetIdentityServerViewModel.kt @@ -33,7 +33,6 @@ import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.terms.TermsService -import org.matrix.android.sdk.internal.util.awaitCallback import java.net.UnknownHostException class SetIdentityServerViewModel @AssistedInject constructor( @@ -97,9 +96,7 @@ class SetIdentityServerViewModel @AssistedInject constructor( viewModelScope.launch { try { // First ping the identity server v2 API - awaitCallback { - mxSession.identityService().isValidIdentityServer(baseUrl, it) - } + mxSession.identityService().isValidIdentityServer(baseUrl) // Ok, next step checkTerms(baseUrl) } catch (failure: Throwable) { From 7986f17c8cc6e21de8cf6d9380c65f37b6daf760 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Tue, 30 Mar 2021 08:56:54 +0100 Subject: [PATCH 127/249] Formatting Signed-off-by: Dominic Fischer --- .../home/room/detail/RoomDetailViewModel.kt | 114 +++++++++--------- .../app/features/widgets/WidgetViewModel.kt | 12 +- 2 files changed, 63 insertions(+), 63 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index adae933e58..017fb8163d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -260,68 +260,68 @@ class RoomDetailViewModel @AssistedInject constructor( override fun handle(action: RoomDetailAction) { when (action) { - is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action) - is RoomDetailAction.ComposerFocusChange -> handleComposerFocusChange(action) - is RoomDetailAction.SaveDraft -> handleSaveDraft(action) - is RoomDetailAction.SendMessage -> handleSendMessage(action) - is RoomDetailAction.SendMedia -> handleSendMedia(action) - is RoomDetailAction.SendSticker -> handleSendSticker(action) - is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action) - is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action) - is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action) - is RoomDetailAction.SendReaction -> handleSendReaction(action) - is RoomDetailAction.AcceptInvite -> handleAcceptInvite() - is RoomDetailAction.RejectInvite -> handleRejectInvite() - is RoomDetailAction.RedactAction -> handleRedactEvent(action) - is RoomDetailAction.UndoReaction -> handleUndoReact(action) - is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action) - is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action) - is RoomDetailAction.EnterEditMode -> handleEditAction(action) - is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action) - is RoomDetailAction.EnterReplyMode -> handleReplyAction(action) - is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action) - is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action) - is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action) - is RoomDetailAction.ResendMessage -> handleResendEvent(action) - is RoomDetailAction.RemoveFailedEcho -> handleRemove(action) - is RoomDetailAction.ResendAll -> handleResendAll() - is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead() - is RoomDetailAction.ReportContent -> handleReportContent(action) - is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action) + is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action) + is RoomDetailAction.ComposerFocusChange -> handleComposerFocusChange(action) + is RoomDetailAction.SaveDraft -> handleSaveDraft(action) + is RoomDetailAction.SendMessage -> handleSendMessage(action) + is RoomDetailAction.SendMedia -> handleSendMedia(action) + is RoomDetailAction.SendSticker -> handleSendSticker(action) + is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action) + is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action) + is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action) + is RoomDetailAction.SendReaction -> handleSendReaction(action) + is RoomDetailAction.AcceptInvite -> handleAcceptInvite() + is RoomDetailAction.RejectInvite -> handleRejectInvite() + is RoomDetailAction.RedactAction -> handleRedactEvent(action) + is RoomDetailAction.UndoReaction -> handleUndoReact(action) + is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action) + is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action) + is RoomDetailAction.EnterEditMode -> handleEditAction(action) + is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action) + is RoomDetailAction.EnterReplyMode -> handleReplyAction(action) + is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action) + is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action) + is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action) + is RoomDetailAction.ResendMessage -> handleResendEvent(action) + is RoomDetailAction.RemoveFailedEcho -> handleRemove(action) + is RoomDetailAction.ResendAll -> handleResendAll() + is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead() + is RoomDetailAction.ReportContent -> handleReportContent(action) + is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action) is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages() - is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages() - is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action) - is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action) - is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) - is RoomDetailAction.RequestVerification -> handleRequestVerification(action) - is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action) - is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action) - is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action) - is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment() - is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager() - is RoomDetailAction.StartCallWithPhoneNumber -> handleStartCallWithPhoneNumber(action) - is RoomDetailAction.StartCall -> handleStartCall(action) - is RoomDetailAction.AcceptCall -> handleAcceptCall(action) - is RoomDetailAction.EndCall -> handleEndCall() - is RoomDetailAction.ManageIntegrations -> handleManageIntegrations() - is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action) - is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId) - is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action) - is RoomDetailAction.CancelSend -> handleCancel(action) - is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action) - is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action) - RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople() - RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar() - is RoomDetailAction.SetAvatarAction -> handleSetNewAvatar(action) - RoomDetailAction.QuickActionSetTopic -> _viewEvents.post(RoomDetailViewEvents.OpenRoomSettings) - is RoomDetailAction.ShowRoomAvatarFullScreen -> { + is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages() + is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action) + is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action) + is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) + is RoomDetailAction.RequestVerification -> handleRequestVerification(action) + is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action) + is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action) + is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action) + is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment() + is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager() + is RoomDetailAction.StartCallWithPhoneNumber -> handleStartCallWithPhoneNumber(action) + is RoomDetailAction.StartCall -> handleStartCall(action) + is RoomDetailAction.AcceptCall -> handleAcceptCall(action) + is RoomDetailAction.EndCall -> handleEndCall() + is RoomDetailAction.ManageIntegrations -> handleManageIntegrations() + is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action) + is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId) + is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action) + is RoomDetailAction.CancelSend -> handleCancel(action) + is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action) + is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action) + RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople() + RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar() + is RoomDetailAction.SetAvatarAction -> handleSetNewAvatar(action) + RoomDetailAction.QuickActionSetTopic -> _viewEvents.post(RoomDetailViewEvents.OpenRoomSettings) + is RoomDetailAction.ShowRoomAvatarFullScreen -> { _viewEvents.post( RoomDetailViewEvents.ShowRoomAvatarFullScreen(action.matrixItem, action.transitionView) ) } - is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action) - RoomDetailAction.RemoveAllFailedMessages -> handleRemoveAllFailedMessages() - RoomDetailAction.ResendAll -> handleResendAll() + is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action) + RoomDetailAction.RemoveAllFailedMessages -> handleRemoveAllFailedMessages() + RoomDetailAction.ResendAll -> handleResendAll() }.exhaustive } diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt index 0388b7b495..b6548a6542 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetViewModel.kt @@ -146,13 +146,13 @@ class WidgetViewModel @AssistedInject constructor(@Assisted val initialState: Wi override fun handle(action: WidgetAction) { when (action) { - is WidgetAction.OnWebViewLoadingError -> handleWebViewLoadingError(action) + is WidgetAction.OnWebViewLoadingError -> handleWebViewLoadingError(action) is WidgetAction.OnWebViewLoadingSuccess -> handleWebViewLoadingSuccess(action) - is WidgetAction.OnWebViewStartedToLoad -> handleWebViewStartLoading() - WidgetAction.LoadFormattedUrl -> loadFormattedUrl(forceFetchToken = false) - WidgetAction.DeleteWidget -> handleDeleteWidget() - WidgetAction.RevokeWidget -> handleRevokeWidget() - WidgetAction.OnTermsReviewed -> loadFormattedUrl(forceFetchToken = false) + is WidgetAction.OnWebViewStartedToLoad -> handleWebViewStartLoading() + WidgetAction.LoadFormattedUrl -> loadFormattedUrl(forceFetchToken = false) + WidgetAction.DeleteWidget -> handleDeleteWidget() + WidgetAction.RevokeWidget -> handleRevokeWidget() + WidgetAction.OnTermsReviewed -> loadFormattedUrl(forceFetchToken = false) } } From 7817b3b0b867dd6496d9e75246093664e74d6bf9 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Tue, 30 Mar 2021 09:00:39 +0100 Subject: [PATCH 128/249] Formatting Signed-off-by: Dominic Fischer --- .../features/home/room/detail/RoomDetailViewModel.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 017fb8163d..c52a41baf4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -659,13 +659,13 @@ class RoomDetailViewModel @AssistedInject constructor( } when (itemId) { R.id.timeline_setting -> true - R.id.invite -> state.canInvite + R.id.invite -> state.canInvite R.id.open_matrix_apps -> true R.id.voice_call, - R.id.video_call -> callManager.getCallsByRoomId(state.roomId).isEmpty() - R.id.hangup_call -> callManager.getCallsByRoomId(state.roomId).isNotEmpty() - R.id.search -> true - R.id.dev_tools -> vectorPreferences.developerMode() + R.id.video_call -> callManager.getCallsByRoomId(state.roomId).isEmpty() + R.id.hangup_call -> callManager.getCallsByRoomId(state.roomId).isNotEmpty() + R.id.search -> true + R.id.dev_tools -> vectorPreferences.developerMode() else -> false } } From 2b933671659ba85323c7ef56e203f41779ffd664 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 29 Mar 2021 21:05:25 +0200 Subject: [PATCH 129/249] Timeline: handle filtering in epoxy --- .../app/core/epoxy/TimelineEmptyItem.kt | 9 ++ .../JumpToBottomViewVisibilityManager.kt | 2 +- .../room/detail/ScrollOnNewMessageCallback.kt | 21 +-- .../timeline/TimelineEventController.kt | 135 ++++++++++++------ .../timeline/factory/DefaultItemFactory.kt | 3 +- .../factory/MergedHeaderItemFactory.kt | 11 +- .../factory/ReadReceiptsItemFactory.kt | 55 +++++++ .../timeline/factory/TimelineItemFactory.kt | 50 +++---- .../helper/MessageInformationDataFactory.kt | 9 -- .../TimelineControllerInterceptorHelper.kt | 37 ++++- .../helper/TimelineDisplayableEvents.kt | 42 +----- .../helper/TimelineEventVisibilityHelper.kt | 131 +++++++++++++++++ .../timeline/item/AbsBaseMessageItem.kt | 11 -- .../detail/timeline/item/BaseEventItem.kt | 1 - .../detail/timeline/item/BasedMergedItem.kt | 3 - .../room/detail/timeline/item/DefaultItem.kt | 7 - .../item/MergedMembershipEventsItem.kt | 3 - .../timeline/item/MergedRoomCreationItem.kt | 3 - .../timeline/item/MessageInformationData.kt | 2 - .../room/detail/timeline/item/NoticeItem.kt | 6 - .../detail/timeline/item/ReadReceiptsItem.kt | 51 +++++++ .../main/res/layout/item_timeline_empty.xml | 2 +- .../res/layout/item_timeline_event_base.xml | 9 -- .../item_timeline_event_base_noinfo.xml | 12 +- .../layout/item_timeline_event_base_state.xml | 8 -- .../item_timeline_event_read_receipts.xml | 14 ++ 26 files changed, 431 insertions(+), 206 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt create mode 100644 vector/src/main/res/layout/item_timeline_event_read_receipts.xml diff --git a/vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt index b77670ba76..9c49a5d458 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt @@ -16,6 +16,7 @@ package im.vector.app.core.epoxy +import androidx.core.view.updateLayoutParams import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R @@ -25,6 +26,14 @@ import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents abstract class TimelineEmptyItem : VectorEpoxyModel(), ItemWithEvents { @EpoxyAttribute lateinit var eventId: String + @EpoxyAttribute var visible: Boolean = false + + override fun bind(holder: Holder) { + super.bind(holder) + holder.view.updateLayoutParams { + this.height = if (visible) 1 else 0 + } + } override fun getEventIds(): List { return listOf(eventId) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/JumpToBottomViewVisibilityManager.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/JumpToBottomViewVisibilityManager.kt index 2810b27aa6..7c0dcbb0d2 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/JumpToBottomViewVisibilityManager.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/JumpToBottomViewVisibilityManager.kt @@ -66,7 +66,7 @@ class JumpToBottomViewVisibilityManager( } private fun maybeShowJumpToBottomViewVisibility() { - if (layoutManager.findFirstVisibleItemPosition() != 0) { + if (layoutManager.findFirstVisibleItemPosition() > 1) { jumpToBottomView.show() } else { jumpToBottomView.hide() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt index fbf9ebe32f..249618e12f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnNewMessageCallback.kt @@ -20,7 +20,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import im.vector.app.core.platform.DefaultListUpdateCallback import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents -import timber.log.Timber +import org.matrix.android.sdk.api.extensions.tryOrNull import java.util.concurrent.CopyOnWriteArrayList class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager, @@ -38,24 +38,27 @@ class ScrollOnNewMessageCallback(private val layoutManager: LinearLayoutManager, } override fun onInserted(position: Int, count: Int) { + if (position != 0) { + return + } if (forceScroll) { forceScroll = false - layoutManager.scrollToPosition(position) + layoutManager.scrollToPosition(0) return } - Timber.v("On inserted $count count at position: $position") - if (layoutManager.findFirstVisibleItemPosition() != position) { + if (layoutManager.findFirstVisibleItemPosition() > 1) { return } - val firstNewItem = timelineEventController.adapter.getModelAtPosition(position) as? ItemWithEvents ?: return + val firstNewItem = tryOrNull { + timelineEventController.adapter.getModelAtPosition(position) + } as? ItemWithEvents ?: return val firstNewItemIds = firstNewItem.getEventIds().firstOrNull() ?: return val indexOfFirstNewItem = newTimelineEventIds.indexOf(firstNewItemIds) if (indexOfFirstNewItem != -1) { - Timber.v("Should scroll to position: $position") - repeat(newTimelineEventIds.size - indexOfFirstNewItem) { - newTimelineEventIds.removeAt(indexOfFirstNewItem) + while (newTimelineEventIds.lastOrNull() != firstNewItemIds) { + newTimelineEventIds.removeLastOrNull() } - layoutManager.scrollToPosition(position) + layoutManager.scrollToPosition(0) } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 44f1e9b759..972736fb2a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -31,16 +31,21 @@ import im.vector.app.core.epoxy.LoadingItem_ import im.vector.app.core.extensions.localDateTime import im.vector.app.core.extensions.nextOrNull import im.vector.app.core.extensions.prevOrNull +import im.vector.app.core.resources.UserPreferencesProvider +import im.vector.app.core.utils.DebouncedClickListener import im.vector.app.features.call.webrtc.WebRtcCallManager +import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.RoomDetailAction import im.vector.app.features.home.room.detail.RoomDetailViewState import im.vector.app.features.home.room.detail.UnreadState import im.vector.app.features.home.room.detail.timeline.factory.MergedHeaderItemFactory +import im.vector.app.features.home.room.detail.timeline.factory.ReadReceiptsItemFactory import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactory import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder import im.vector.app.features.home.room.detail.timeline.helper.TimelineControllerInterceptorHelper import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback +import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityStateChangedListener import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem @@ -49,6 +54,8 @@ import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData +import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem +import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem_ import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.media.ImageContentRenderer @@ -58,6 +65,7 @@ import org.matrix.android.sdk.api.session.Session 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 +import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent @@ -65,8 +73,6 @@ import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject -private const val DEFAULT_PREFETCH_THRESHOLD = 30 - class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter, private val vectorPreferences: VectorPreferences, private val contentUploadStateTrackerBinder: ContentUploadStateTrackerBinder, @@ -77,7 +83,10 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private val session: Session, private val callManager: WebRtcCallManager, @TimelineEventControllerHandler - private val backgroundHandler: Handler + private val backgroundHandler: Handler, + private val userPreferencesProvider: UserPreferencesProvider, + private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper, + private val readReceiptsItemFactory: ReadReceiptsItemFactory ) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener, EpoxyController.Interceptor { interface Callback : @@ -147,7 +156,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private var unreadState: UnreadState = UnreadState.Unknown private var positionOfReadMarker: Int? = null private var eventIdToHighlight: String? = null - private var previousModelsSize = 0 var callback: Callback? = null var timeline: Timeline? = null @@ -198,7 +206,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private val interceptorHelper = TimelineControllerInterceptorHelper( ::positionOfReadMarker, adapterPositionMapping, - vectorPreferences, + userPreferencesProvider, callManager ) @@ -311,7 +319,9 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec } else { cacheItemData.eventModel } - listOf(eventModel, + listOf( + cacheItemData?.readReceiptsItem?.takeIf { cacheItemData.mergedHeaderModel == null }, + eventModel, cacheItemData?.mergedHeaderModel, cacheItemData?.formattedDayModel?.takeIf { eventModel != null || cacheItemData.mergedHeaderModel != null } ) @@ -323,61 +333,94 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private fun buildCacheItemsIfNeeded() = synchronized(modelCache) { hasUTD = false hasReachedInvite = false - if (modelCache.isEmpty()) { return } + val receiptsByEvents = getReadReceiptsByShownEvent() (0 until modelCache.size).forEach { position -> - // Should be build if not cached or if cached but contains additional models - // We then are sure we always have items up to date. - if (modelCache[position] == null || modelCache[position]?.shouldTriggerBuild() == true) { - modelCache[position] = buildCacheItem(position, currentSnapshot) + val event = currentSnapshot[position] + val nextEvent = currentSnapshot.nextOrNull(position) + val prevEvent = currentSnapshot.prevOrNull(position) + // Should be build if not cached or if model should be refreshed + if (modelCache[position] == null || modelCache[position]?.shouldTriggerBuild == true) { + modelCache[position] = buildCacheItem(event, nextEvent, prevEvent) } + val itemCachedData = modelCache[position] ?: return@forEach + // Then update with additional models if needed + modelCache[position] = itemCachedData.enrichWithModels(event, nextEvent, position, receiptsByEvents) } } - private fun buildCacheItem(currentPosition: Int, items: List): CacheItemData { - val event = items[currentPosition] - val nextEvent = items.nextOrNull(currentPosition) - val prevEvent = items.prevOrNull(currentPosition) + private fun buildCacheItem(event: TimelineEvent, + nextEvent: TimelineEvent?, + prevEvent: TimelineEvent? + ): CacheItemData { if (hasReachedInvite && hasUTD) { - return CacheItemData(event.localId, event.root.eventId, null, null, null) + return CacheItemData(event.localId, event.root.eventId) } updateUTDStates(event, nextEvent) val eventModel = timelineItemFactory.create(event, prevEvent, nextEvent, eventIdToHighlight, callback).also { it.id(event.localId) it.setOnVisibilityStateChanged(TimelineEventVisibilityStateChangedListener(callback, event)) } - val addDaySeparator = if (hasReachedInvite && hasUTD) { - true - } else { - val date = event.root.localDateTime() - val nextDate = nextEvent?.root?.localDateTime() - date.toLocalDate() != nextDate?.toLocalDate() - } + val shouldTriggerBuild = eventModel is AbsMessageItem && eventModel.attributes.informationData.sendStateDecoration == SendStateDecoration.SENT + return CacheItemData( + localId = event.localId, + eventId = event.root.eventId, + eventModel = eventModel, + shouldTriggerBuild = shouldTriggerBuild) + } + + private fun CacheItemData.enrichWithModels(event: TimelineEvent, + nextEvent: TimelineEvent?, + position: Int, + receiptsByEvents: Map>): CacheItemData { + val wantsDateSeparator = wantsDateSeparator(event, nextEvent) val mergedHeaderModel = mergedHeaderItemFactory.create(event, nextEvent = nextEvent, - items = items, - addDaySeparator = addDaySeparator, - currentPosition = currentPosition, + items = this@TimelineEventController.currentSnapshot, + addDaySeparator = wantsDateSeparator, + currentPosition = position, eventIdToHighlight = eventIdToHighlight, callback = callback ) { requestModelBuild() } - val daySeparatorItem = buildDaySeparatorItem(addDaySeparator, event.root.originServerTs) - // If we have a SENT decoration, we want to built again as it might have to be changed to NONE if more recent event has also SENT decoration - val forceTriggerBuild = eventModel is AbsMessageItem && eventModel.attributes.informationData.sendStateDecoration == SendStateDecoration.SENT - return CacheItemData(event.localId, event.root.eventId, eventModel, mergedHeaderModel, daySeparatorItem, forceTriggerBuild) - } - - private fun buildDaySeparatorItem(addDaySeparator: Boolean, originServerTs: Long?): DaySeparatorItem? { - return if (addDaySeparator) { - val formattedDay = dateFormatter.format(originServerTs, DateFormatKind.TIMELINE_DAY_DIVIDER) - DaySeparatorItem_().formattedDay(formattedDay).id(formattedDay) + val formattedDayModel = if (wantsDateSeparator) { + buildDaySeparatorItem(event.root.originServerTs) } else { null } + val readReceipts = receiptsByEvents[event.eventId].orEmpty() + return copy( + readReceiptsItem = readReceiptsItemFactory.create(event.eventId, readReceipts, callback), + formattedDayModel = formattedDayModel, + mergedHeaderModel = mergedHeaderModel + ) + } + + private fun getReadReceiptsByShownEvent(): Map> { + val receiptsByEvent = HashMap>() + var lastShownEventId: String? = null + val itr = currentSnapshot.listIterator(currentSnapshot.size) + while (itr.hasPrevious()) { + val event = itr.previous() + val currentReadReceipts = ArrayList(event.readReceipts) + if (timelineEventVisibilityHelper.shouldShowEvent(event, eventIdToHighlight)) { + lastShownEventId = event.eventId + } + if (lastShownEventId == null) { + continue + } + val existingReceipts = receiptsByEvent.getOrPut(lastShownEventId) { ArrayList() } + existingReceipts.addAll(currentReadReceipts) + } + return receiptsByEvent + } + + private fun buildDaySeparatorItem(originServerTs: Long?): DaySeparatorItem { + val formattedDay = dateFormatter.format(originServerTs, DateFormatKind.TIMELINE_DAY_DIVIDER) + return DaySeparatorItem_().formattedDay(formattedDay).id(formattedDay) } private fun LoadingItem_.setVisibilityStateChangedListener(direction: Timeline.Direction): LoadingItem_ { @@ -409,6 +452,16 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec } } + private fun wantsDateSeparator(event: TimelineEvent, nextEvent: TimelineEvent?): Boolean { + return if (hasReachedInvite && hasUTD) { + true + } else { + val date = event.root.localDateTime() + val nextDate = nextEvent?.root?.localDateTime() + date.toLocalDate() != nextDate?.toLocalDate() + } + } + /** * Return true if added */ @@ -429,14 +482,10 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private data class CacheItemData( val localId: Long, val eventId: String?, + val readReceiptsItem: ReadReceiptsItem? = null, val eventModel: EpoxyModel<*>? = null, val mergedHeaderModel: BasedMergedItem<*>? = null, val formattedDayModel: DaySeparatorItem? = null, - val forceTriggerBuild: Boolean = false - ) { - fun shouldTriggerBuild(): Boolean { - // Since those items can change when we paginate, force a re-build - return forceTriggerBuild || mergedHeaderModel != null || formattedDayModel != null - } - } + val shouldTriggerBuild: Boolean = false + ) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/DefaultItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/DefaultItemFactory.kt index 71ac46307b..5f5d3f5156 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/DefaultItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/DefaultItemFactory.kt @@ -43,8 +43,7 @@ class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: Ava text = text, itemLongClickListener = { view -> callback?.onEventLongClicked(informationData, null, view) ?: false - }, - readReceiptsCallback = callback + } ) return DefaultItem_() .leftGuideline(avatarSizeProvider.leftGuideline) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index 2134645d8d..4e4a7fce02 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -23,9 +23,9 @@ import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.MergedTimelineEventVisibilityStateChangedListener import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder +import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper import im.vector.app.features.home.room.detail.timeline.helper.canBeMerged import im.vector.app.features.home.room.detail.timeline.helper.isRoomConfiguration -import im.vector.app.features.home.room.detail.timeline.helper.prevSameTypeEvents import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem import im.vector.app.features.home.room.detail.timeline.item.MergedMembershipEventsItem import im.vector.app.features.home.room.detail.timeline.item.MergedMembershipEventsItem_ @@ -47,7 +47,8 @@ import javax.inject.Inject class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolder: ActiveSessionHolder, private val avatarRenderer: AvatarRenderer, private val avatarSizeProvider: AvatarSizeProvider, - private val roomSummariesHolder: RoomSummariesHolder) { + private val roomSummariesHolder: RoomSummariesHolder, +private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper) { private val collapsedEventIds = linkedSetOf() private val mergeItemCollapseStates = HashMap() @@ -85,7 +86,7 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde eventIdToHighlight: String?, requestModelBuild: () -> Unit, callback: TimelineEventController.Callback?): MergedMembershipEventsItem_? { - val prevSameTypeEvents = items.prevSameTypeEvents(currentPosition, 2) + val prevSameTypeEvents = timelineEventVisibilityHelper.prevSameTypeEvents(items, currentPosition, 2) return if (prevSameTypeEvents.isEmpty()) { null } else { @@ -126,8 +127,7 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde onCollapsedStateChanged = { mergeItemCollapseStates[event.localId] = it requestModelBuild() - }, - readReceiptsCallback = callback + } ) MergedMembershipEventsItem_() .id(mergeId) @@ -205,7 +205,6 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde }, hasEncryptionEvent = hasEncryption, isEncryptionAlgorithmSecure = encryptionAlgorithm == MXCRYPTO_ALGORITHM_MEGOLM, - readReceiptsCallback = callback, callback = callback, currentUserId = currentUserId, roomSummary = roomSummariesHolder.get(event.roomId), diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt new file mode 100644 index 0000000000..a3e8541b05 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt @@ -0,0 +1,55 @@ +/* + * 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.detail.timeline.factory + +import im.vector.app.core.utils.DebouncedClickListener +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData +import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem +import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem_ +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.model.ReadReceipt +import javax.inject.Inject + +class ReadReceiptsItemFactory @Inject constructor(private val session: Session, + private val avatarRenderer: AvatarRenderer) { + + fun create(eventId: String, readReceipts: List, callback: TimelineEventController.Callback?): ReadReceiptsItem? { + val readReceiptsData = readReceipts + .asSequence() + .filter { + it.user.userId != session.myUserId + } + .map { + ReadReceiptData(it.user.userId, it.user.avatarUrl, it.user.displayName, it.originServerTs) + } + .toList() + + if (readReceiptsData.isEmpty()) { + return null + } + return ReadReceiptsItem_() + .id("read_receipts_$eventId") + .eventId(eventId) + .readReceipts(readReceiptsData) + .avatarRenderer(avatarRenderer) + .clickListener(DebouncedClickListener({ _ -> + callback?.onReadReceiptsClicked(readReceiptsData) + })) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 73f101d1f5..df4eab0efe 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -21,6 +21,7 @@ import im.vector.app.core.epoxy.TimelineEmptyItem_ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import timber.log.Timber @@ -35,7 +36,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me private val widgetItemFactory: WidgetItemFactory, private val verificationConclusionItemFactory: VerificationItemFactory, private val callItemFactory: CallItemFactory, - private val userPreferencesProvider: UserPreferencesProvider) { + private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper) { /** * Reminder: nextEvent is older and prevEvent is newer. @@ -46,12 +47,14 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me eventIdToHighlight: String?, callback: TimelineEventController.Callback?): VectorEpoxyModel<*> { val highlight = event.root.eventId == eventIdToHighlight - val computedModel = try { + if (!timelineEventVisibilityHelper.shouldShowEvent(event, eventIdToHighlight)) { + return buildEmptyItem(event, prevEvent, eventIdToHighlight) + } when (event.root.getClearType()) { + // Message items EventType.STICKER, EventType.MESSAGE -> messageItemFactory.create(event, prevEvent, nextEvent, highlight, callback) - // State and call EventType.STATE_ROOM_TOMBSTONE, EventType.STATE_ROOM_NAME, EventType.STATE_ROOM_TOPIC, @@ -63,8 +66,19 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.STATE_ROOM_HISTORY_VISIBILITY, EventType.STATE_ROOM_SERVER_ACL, EventType.STATE_ROOM_GUEST_ACCESS, - EventType.STATE_ROOM_POWER_LEVELS, - EventType.REDACTION -> noticeItemFactory.create(event, highlight, callback) + EventType.REDACTION , + EventType.STATE_ROOM_ALIASES, + EventType.KEY_VERIFICATION_ACCEPT, + EventType.KEY_VERIFICATION_START, + EventType.KEY_VERIFICATION_KEY, + EventType.KEY_VERIFICATION_READY, + EventType.KEY_VERIFICATION_MAC, + EventType.CALL_CANDIDATES, + EventType.CALL_REPLACES, + EventType.CALL_SELECT_ANSWER, + EventType.CALL_NEGOTIATE, + EventType.REACTION, + EventType.STATE_ROOM_POWER_LEVELS -> noticeItemFactory.create(event, highlight, callback) EventType.STATE_ROOM_WIDGET_LEGACY, EventType.STATE_ROOM_WIDGET -> widgetItemFactory.create(event, highlight, callback) EventType.STATE_ROOM_ENCRYPTION -> encryptionItemFactory.create(event, highlight, callback) @@ -84,30 +98,10 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me encryptedItemFactory.create(event, prevEvent, nextEvent, highlight, callback) } } - EventType.STATE_ROOM_ALIASES, - EventType.KEY_VERIFICATION_ACCEPT, - EventType.KEY_VERIFICATION_START, - EventType.KEY_VERIFICATION_KEY, - EventType.KEY_VERIFICATION_READY, - EventType.KEY_VERIFICATION_MAC, - EventType.REACTION, - EventType.CALL_CANDIDATES, - EventType.CALL_REPLACES, - EventType.CALL_SELECT_ANSWER, - EventType.CALL_NEGOTIATE -> { - // TODO These are not filtered out by timeline when encrypted - // For now manually ignore - if (userPreferencesProvider.shouldShowHiddenEvents()) { - noticeItemFactory.create(event, highlight, callback) - } else { - null - } - } EventType.KEY_VERIFICATION_CANCEL, EventType.KEY_VERIFICATION_DONE -> { verificationConclusionItemFactory.create(event, highlight, callback) } - // Unhandled event types else -> { // Should only happen when shouldShowHiddenEvents() settings is ON @@ -119,12 +113,14 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me Timber.e(throwable, "failed to create message item") defaultItemFactory.create(event, highlight, callback, throwable) } - return computedModel ?: buildEmptyItem(event) + return computedModel ?: buildEmptyItem(event, prevEvent, eventIdToHighlight) } - private fun buildEmptyItem(timelineEvent: TimelineEvent): TimelineEmptyItem { + private fun buildEmptyItem(timelineEvent: TimelineEvent, prevEvent: TimelineEvent?, eventIdToHighlight: String?): TimelineEmptyItem { + val makesEmptyItemVisible = prevEvent == null || timelineEventVisibilityHelper.shouldShowEvent(prevEvent, eventIdToHighlight) return TimelineEmptyItem_() .id(timelineEvent.localId) .eventId(timelineEvent.eventId) + .visible(makesEmptyItemVisible) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index 09f173de14..abaa2ffa17 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -111,15 +111,6 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses }, hasBeenEdited = event.hasBeenEdited(), hasPendingEdits = event.annotations?.editSummary?.localEchos?.any() ?: false, - readReceipts = event.readReceipts - .asSequence() - .filter { - it.user.userId != session.myUserId - } - .map { - ReadReceiptData(it.user.userId, it.user.avatarUrl, it.user.displayName, it.originServerTs) - } - .toList(), referencesInfoData = event.annotations?.referencesAggregatedSummary?.let { referencesAggregatedSummary -> val verificationState = referencesAggregatedSummary.content.toModel()?.verificationState ?: VerificationState.REQUEST diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt index 971a3a35d8..392cb0ae57 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt @@ -19,11 +19,14 @@ package im.vector.app.features.home.room.detail.timeline.helper import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.VisibilityState import im.vector.app.core.epoxy.LoadingItem_ +import im.vector.app.core.epoxy.TimelineEmptyItem import im.vector.app.core.epoxy.TimelineEmptyItem_ +import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.home.room.detail.UnreadState import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem +import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents import im.vector.app.features.home.room.detail.timeline.item.TimelineReadMarkerItem_ import im.vector.app.features.settings.VectorPreferences @@ -34,7 +37,7 @@ private const val DEFAULT_PREFETCH_THRESHOLD = 30 class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMutableProperty0, private val adapterPositionMapping: MutableMap, - private val vectorPreferences: VectorPreferences, + private val userPreferencesProvider: UserPreferencesProvider, private val callManager: WebRtcCallManager ) { @@ -56,23 +59,40 @@ class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMut models.addForwardPrefetchIfNeeded(timeline, callback) val modelsIterator = models.listIterator() - val showHiddenEvents = vectorPreferences.shouldShowHiddenEvents() + val showHiddenEvents = userPreferencesProvider.shouldShowHiddenEvents() var index = 0 val firstUnreadEventId = (unreadState as? UnreadState.HasUnread)?.firstUnreadEventId + var atLeastOneVisibleItemSinceLastDaySeparator = false + var atLeastOneVisibleItemsBeforeReadMarker = false + // Then iterate on models so we have the exact positions in the adapter modelsIterator.forEach { epoxyModel -> + if(epoxyModel !is TimelineEmptyItem){ + atLeastOneVisibleItemSinceLastDaySeparator = true + atLeastOneVisibleItemsBeforeReadMarker = true + } if (epoxyModel is ItemWithEvents) { epoxyModel.getEventIds().forEach { eventId -> adapterPositionMapping[eventId] = index - if (eventId == firstUnreadEventId) { + if (eventId == firstUnreadEventId && atLeastOneVisibleItemsBeforeReadMarker) { modelsIterator.addReadMarkerItem(callback) index++ positionOfReadMarker.set(index) } } } - if (epoxyModel is CallTileTimelineItem) { - modelsIterator.removeCallItemIfNeeded(epoxyModel, callIds, showHiddenEvents) + if(epoxyModel is DaySeparatorItem){ + if(!atLeastOneVisibleItemSinceLastDaySeparator){ + modelsIterator.remove() + return@forEach + } + atLeastOneVisibleItemSinceLastDaySeparator = false + } + else if (epoxyModel is CallTileTimelineItem) { + val hasBeenRemoved = modelsIterator.removeCallItemIfNeeded(epoxyModel, callIds, showHiddenEvents) + if(!hasBeenRemoved){ + atLeastOneVisibleItemSinceLastDaySeparator = true + } } index++ } @@ -94,20 +114,23 @@ class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMut epoxyModel: CallTileTimelineItem, callIds: MutableSet, showHiddenEvents: Boolean - ) { + ): Boolean { val callId = epoxyModel.attributes.callId // We should remove the call tile if we already have one for this call or // if this is an active call tile without an actual call (which can happen with permalink) val shouldRemoveCallItem = callIds.contains(callId) || (!callManager.getAdvertisedCalls().contains(callId) && epoxyModel.attributes.callStatus.isActive()) - if (shouldRemoveCallItem && !showHiddenEvents) { + val removed = shouldRemoveCallItem && !showHiddenEvents + if (removed) { remove() val emptyItem = TimelineEmptyItem_() .id(epoxyModel.id()) .eventId(epoxyModel.attributes.informationData.eventId) + .visible(false) add(emptyItem) } callIds.add(callId) + return removed } private fun MutableList>.addBackwardPrefetchIfNeeded(timeline: Timeline?, callback: TimelineEventController.Callback?) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index eb5b8081f9..a597fb966e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -22,6 +22,9 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent object TimelineDisplayableEvents { + /** + * All types we have an item to build with. Every type not defined here will be shown as DefaultItem if forced to be shown, otherwise will be hidden. + */ val DISPLAYABLE_TYPES = listOf( EventType.MESSAGE, EventType.STATE_ROOM_WIDGET_LEGACY, @@ -50,6 +53,7 @@ object TimelineDisplayableEvents { EventType.KEY_VERIFICATION_DONE, EventType.KEY_VERIFICATION_CANCEL ) + } fun TimelineEvent.canBeMerged(): Boolean { @@ -68,7 +72,7 @@ fun TimelineEvent.isRoomConfiguration(roomCreatorUserId: String?): Boolean { EventType.STATE_ROOM_CANONICAL_ALIAS, EventType.STATE_ROOM_POWER_LEVELS, EventType.STATE_ROOM_ENCRYPTION -> true - EventType.STATE_ROOM_MEMBER -> { + EventType.STATE_ROOM_MEMBER -> { // Keep only room member events regarding the room creator (when he joined the room), // but exclude events where the room creator invite others, or where others join roomCreatorUserId != null && root.stateKey == roomCreatorUserId @@ -76,39 +80,3 @@ fun TimelineEvent.isRoomConfiguration(roomCreatorUserId: String?): Boolean { else -> false } } - -fun List.nextSameTypeEvents(index: Int, minSize: Int): List { - if (index >= size - 1) { - return emptyList() - } - val timelineEvent = this[index] - val nextSubList = subList(index + 1, size) - val indexOfNextDay = nextSubList.indexOfFirst { - val date = it.root.localDateTime() - val nextDate = timelineEvent.root.localDateTime() - date.toLocalDate() != nextDate.toLocalDate() - } - val nextSameDayEvents = if (indexOfNextDay == -1) { - nextSubList - } else { - nextSubList.subList(0, indexOfNextDay) - } - val indexOfFirstDifferentEventType = nextSameDayEvents.indexOfFirst { it.root.getClearType() != timelineEvent.root.getClearType() } - val sameTypeEvents = if (indexOfFirstDifferentEventType == -1) { - nextSameDayEvents - } else { - nextSameDayEvents.subList(0, indexOfFirstDifferentEventType) - } - if (sameTypeEvents.size < minSize) { - return emptyList() - } - return sameTypeEvents -} - -fun List.prevSameTypeEvents(index: Int, minSize: Int): List { - val prevSub = subList(0, index + 1) - return prevSub - .reversed() - .nextSameTypeEvents(0, minSize) - .reversed() -} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt new file mode 100644 index 0000000000..54d76bc681 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt @@ -0,0 +1,131 @@ +/* + * 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.detail.timeline.helper + +import im.vector.app.core.extensions.localDateTime +import im.vector.app.core.resources.UserPreferencesProvider +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.getRelationContent +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.timeline.TimelineEvent +import javax.inject.Inject + +class TimelineEventVisibilityHelper @Inject constructor(private val userPreferencesProvider: UserPreferencesProvider) { + + fun nextSameTypeEvents(timelineEvents: List, index: Int, minSize: Int): List { + if (index >= timelineEvents.size - 1) { + return emptyList() + } + val timelineEvent = timelineEvents[index] + val nextSubList = timelineEvents.subList(index + 1, timelineEvents.size) + val indexOfNextDay = nextSubList.indexOfFirst { + val date = it.root.localDateTime() + val nextDate = timelineEvent.root.localDateTime() + date.toLocalDate() != nextDate.toLocalDate() + } + val nextSameDayEvents = if (indexOfNextDay == -1) { + nextSubList + } else { + nextSubList.subList(0, indexOfNextDay) + } + val indexOfFirstDifferentEventType = nextSameDayEvents.indexOfFirst { it.root.getClearType() != timelineEvent.root.getClearType() } + val sameTypeEvents = if (indexOfFirstDifferentEventType == -1) { + nextSameDayEvents + } else { + nextSameDayEvents.subList(0, indexOfFirstDifferentEventType) + } + val filteredSameTypeEvents = sameTypeEvents.filter { shouldShowEvent(it)} + if (filteredSameTypeEvents.size < minSize) { + return emptyList() + } + return filteredSameTypeEvents + } + + fun prevSameTypeEvents(timelineEvents: List, index: Int, minSize: Int): List { + val prevSub = timelineEvents.subList(0, index + 1) + return prevSub + .reversed() + .let { + nextSameTypeEvents(it, 0, minSize) + } + .reversed() + } + + fun shouldShowEvent(timelineEvent: TimelineEvent, highlightEventId: String? = null): Boolean { + // If show hidden events is true we should always display something + if (userPreferencesProvider.shouldShowHiddenEvents()) { + return true + } + // We always show highlighted event + if (timelineEvent.eventId == highlightEventId) { + return true + } + if (!timelineEvent.isDisplayable()) { + return false + } + // Check for special case where we should hide the event, like redacted, relation, memberships... according to user preferences. + return !timelineEvent.shouldBeHidden() + } + + private fun TimelineEvent.isDisplayable(): Boolean { + return TimelineDisplayableEvents.DISPLAYABLE_TYPES.contains(root.getClearType()) + } + + private fun TimelineEvent.shouldBeHidden(): Boolean { + if (root.isRedacted() && !userPreferencesProvider.shouldShowRedactedMessages()) { + return true + } + if (root.getRelationContent()?.type == RelationType.REPLACE) { + return true + } + if (root.getClearType() == EventType.STATE_ROOM_MEMBER) { + val diff = computeMembershipDiff() + if ((diff.isJoin || diff.isPart) && !userPreferencesProvider.shouldShowRoomMemberStateEvents()) return true + } + return false + } + + private fun TimelineEvent.computeMembershipDiff(): MembershipDiff { + val content = root.getClearContent().toModel() + val prevContent = root.resolvedPrevContent().toModel() + + val isMembershipChanged = content?.membership != prevContent?.membership; + val isJoin = isMembershipChanged && content?.membership == Membership.JOIN + val isPart = isMembershipChanged && content?.membership == Membership.LEAVE && root.stateKey == root.senderId + + val isJoinToJoin = !isMembershipChanged && content?.membership == Membership.JOIN + val isDisplaynameChange = isJoinToJoin && content?.displayName != prevContent?.displayName; + val isAvatarChange = isJoinToJoin && content?.avatarUrl !== prevContent?.avatarUrl + + return MembershipDiff( + isJoin = isJoin, + isPart = isPart, + isDisplaynameChange = isDisplaynameChange, + isAvatarChange = isAvatarChange + ) + } + + private data class MembershipDiff( + val isJoin: Boolean, + val isPart: Boolean, + val isDisplaynameChange: Boolean, + val isAvatarChange: Boolean + ) +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt index a65f1e10f2..a5f3f7c547 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt @@ -41,10 +41,6 @@ abstract class AbsBaseMessageItem : BaseEventItem abstract val baseAttributes: Attributes - private val _readReceiptsClickListener = DebouncedClickListener({ - baseAttributes.readReceiptsCallback?.onReadReceiptsClicked(baseAttributes.informationData.readReceipts) - }) - private var reactionClickListener: ReactionButton.ReactedListener = object : ReactionButton.ReactedListener { override fun onReacted(reactionButton: ReactionButton) { baseAttributes.reactionPillCallback?.onClickOnReactionPill(baseAttributes.informationData, reactionButton.reactionString, true) @@ -69,12 +65,6 @@ abstract class AbsBaseMessageItem : BaseEventItem override fun bind(holder: H) { super.bind(holder) - holder.readReceiptsView.render( - baseAttributes.informationData.readReceipts, - baseAttributes.avatarRenderer, - _readReceiptsClickListener - ) - val reactions = baseAttributes.informationData.orderedReactionList if (!shouldShowReactionAtBottom() || reactions.isNullOrEmpty()) { holder.reactionsContainer.isVisible = false @@ -111,7 +101,6 @@ abstract class AbsBaseMessageItem : BaseEventItem override fun unbind(holder: H) { holder.reactionsContainer.setOnLongClickListener(null) - holder.readReceiptsView.unbind(baseAttributes.avatarRenderer) super.unbind(holder) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt index 13bb6db6ef..aae1edbed1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt @@ -56,7 +56,6 @@ abstract class BaseEventItem : VectorEpoxyModel abstract class BaseHolder(@IdRes val stubId: Int) : VectorEpoxyHolder() { val leftGuideline by bind(R.id.messageStartGuideline) val checkableBackground by bind(R.id.messageSelectedBackground) - val readReceiptsView by bind(R.id.readReceiptsView) override fun bindView(itemView: View) { super.bindView(itemView) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt index 1f8ad3df1b..8a49bd6803 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt @@ -41,8 +41,6 @@ abstract class BasedMergedItem : BaseEventItem() holder.separatorView.visibility = View.VISIBLE holder.expandView.setText(R.string.merged_events_collapse) } - // No read receipt for this item - holder.readReceiptsView.isVisible = false } protected val distinctMergeData by lazy { @@ -72,7 +70,6 @@ abstract class BasedMergedItem : BaseEventItem() val isCollapsed: Boolean val mergeData: List val avatarRenderer: AvatarRenderer - val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? val onCollapsedStateChanged: (Boolean) -> Unit } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultItem.kt index cdc677334e..580a56bf05 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultItem.kt @@ -32,21 +32,15 @@ abstract class DefaultItem : BaseEventItem() { @EpoxyAttribute lateinit var attributes: Attributes - private val _readReceiptsClickListener = DebouncedClickListener({ - attributes.readReceiptsCallback?.onReadReceiptsClicked(attributes.informationData.readReceipts) - }) - override fun bind(holder: Holder) { super.bind(holder) holder.messageTextView.text = attributes.text attributes.avatarRenderer.render(attributes.informationData.matrixItem, holder.avatarImageView) holder.view.setOnLongClickListener(attributes.itemLongClickListener) - holder.readReceiptsView.render(attributes.informationData.readReceipts, attributes.avatarRenderer, _readReceiptsClickListener) } override fun unbind(holder: Holder) { attributes.avatarRenderer.clear(holder.avatarImageView) - holder.readReceiptsView.unbind(attributes.avatarRenderer) super.unbind(holder) } @@ -66,7 +60,6 @@ abstract class DefaultItem : BaseEventItem() { val informationData: MessageInformationData, val text: CharSequence, val itemLongClickListener: View.OnLongClickListener? = null, - val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null ) companion object { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt index ef4a6662b4..70a6864a1f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt @@ -56,8 +56,6 @@ abstract class MergedMembershipEventsItem : BasedMergedItem, override val avatarRenderer: AvatarRenderer, - override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, override val onCollapsedStateChanged: (Boolean) -> Unit ) : BasedMergedItem.Attributes } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt index 6a665bb44f..9faef589ca 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt @@ -92,8 +92,6 @@ abstract class MergedRoomCreationItem : BasedMergedItem, override val avatarRenderer: AvatarRenderer, - override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, override val onCollapsedStateChanged: (Boolean) -> Unit, val callback: TimelineEventController.Callback? = null, val currentUserId: String, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt index 67b79bab9b..08aa301538 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt @@ -36,10 +36,8 @@ data class MessageInformationData( /*List of reactions (emoji,count,isSelected)*/ val orderedReactionList: List? = null, val pollResponseAggregatedSummary: PollResponseData? = null, - val hasBeenEdited: Boolean = false, val hasPendingEdits: Boolean = false, - val readReceipts: List = emptyList(), val referencesInfoData: ReferencesInfoData? = null, val sentByMe: Boolean, val e2eDecoration: E2EDecoration = E2EDecoration.NONE, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt index bcf170dc4d..b733d0ec32 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt @@ -36,16 +36,11 @@ abstract class NoticeItem : BaseEventItem() { @EpoxyAttribute lateinit var attributes: Attributes - private val _readReceiptsClickListener = DebouncedClickListener({ - attributes.readReceiptsCallback?.onReadReceiptsClicked(attributes.informationData.readReceipts) - }) - override fun bind(holder: Holder) { super.bind(holder) holder.noticeTextView.text = attributes.noticeText attributes.avatarRenderer.render(attributes.informationData.matrixItem, holder.avatarImageView) holder.view.setOnLongClickListener(attributes.itemLongClickListener) - holder.readReceiptsView.render(attributes.informationData.readReceipts, attributes.avatarRenderer, _readReceiptsClickListener) holder.avatarImageView.onClick(attributes.avatarClickListener) when (attributes.informationData.e2eDecoration) { @@ -62,7 +57,6 @@ abstract class NoticeItem : BaseEventItem() { override fun unbind(holder: Holder) { attributes.avatarRenderer.clear(holder.avatarImageView) - holder.readReceiptsView.unbind(attributes.avatarRenderer) super.unbind(holder) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt new file mode 100644 index 0000000000..84b2662687 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt @@ -0,0 +1,51 @@ +/* + * 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.detail.timeline.item + +import android.view.View +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import com.airbnb.epoxy.EpoxyModelWithHolder +import im.vector.app.R +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.ui.views.ReadReceiptsView +import im.vector.app.features.home.AvatarRenderer + +@EpoxyModelClass(layout = R.layout.item_timeline_event_read_receipts) +abstract class ReadReceiptsItem : EpoxyModelWithHolder(), ItemWithEvents { + + @EpoxyAttribute lateinit var eventId: String + @EpoxyAttribute lateinit var readReceipts: List + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var avatarRenderer: AvatarRenderer + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var clickListener: View.OnClickListener + + override fun getEventIds(): List = listOf(eventId) + + override fun bind(holder: Holder) { + super.bind(holder) + holder.readReceiptsView.render(readReceipts, avatarRenderer, clickListener) + } + + override fun unbind(holder: Holder) { + holder.readReceiptsView.unbind(avatarRenderer) + super.unbind(holder) + } + + class Holder : VectorEpoxyHolder() { + val readReceiptsView by bind(R.id.readReceiptsView) + } +} diff --git a/vector/src/main/res/layout/item_timeline_empty.xml b/vector/src/main/res/layout/item_timeline_empty.xml index c8dee60cc7..562cbd39ba 100644 --- a/vector/src/main/res/layout/item_timeline_empty.xml +++ b/vector/src/main/res/layout/item_timeline_empty.xml @@ -1,4 +1,4 @@ \ No newline at end of file + android:layout_height="0dp" /> diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index ce3460a21c..f9562f65b0 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -188,15 +188,6 @@ android:layout_height="wrap_content" /--> - - - \ No newline at end of file diff --git a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml index 6442f230d5..35e1b097d7 100644 --- a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml +++ b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml @@ -10,7 +10,7 @@ android:id="@+id/messageSelectedBackground" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_alignBottom="@+id/readReceiptsView" + android:layout_alignParentBottom="true" android:layout_alignParentTop="true" android:background="@drawable/highlighted_message_background" /> @@ -80,14 +80,4 @@ android:visibility="gone" tools:visibility="visible" /> - - - \ No newline at end of file diff --git a/vector/src/main/res/layout/item_timeline_event_base_state.xml b/vector/src/main/res/layout/item_timeline_event_base_state.xml index db5ed052f3..98cea901da 100644 --- a/vector/src/main/res/layout/item_timeline_event_base_state.xml +++ b/vector/src/main/res/layout/item_timeline_event_base_state.xml @@ -120,14 +120,6 @@ - - \ No newline at end of file diff --git a/vector/src/main/res/layout/item_timeline_event_read_receipts.xml b/vector/src/main/res/layout/item_timeline_event_read_receipts.xml new file mode 100644 index 0000000000..f741e434c7 --- /dev/null +++ b/vector/src/main/res/layout/item_timeline_event_read_receipts.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file From 13cb81b92f6b995a318f964df12351bfb23b9ae8 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 30 Mar 2021 18:07:05 +0200 Subject: [PATCH 130/249] Timeline: fix SendState decoration + some filtering issues --- .../app/core/epoxy/TimelineEmptyItem.kt | 7 ++- .../timeline/TimelineEventController.kt | 51 +++++++++++++++---- .../timeline/factory/CallItemFactory.kt | 24 ++++----- .../timeline/factory/DefaultItemFactory.kt | 11 ++-- .../timeline/factory/EncryptedItemFactory.kt | 17 +++---- .../timeline/factory/EncryptionItemFactory.kt | 13 ++--- .../factory/MergedHeaderItemFactory.kt | 2 +- .../timeline/factory/MessageItemFactory.kt | 17 +++---- .../timeline/factory/NoticeItemFactory.kt | 17 +++---- .../factory/ReadReceiptsItemFactory.kt | 14 ++--- .../timeline/factory/RoomCreateItemFactory.kt | 13 +++-- .../timeline/factory/TimelineItemFactory.kt | 46 ++++++++--------- .../factory/TimelineItemFactoryParams.kt | 33 ++++++++++++ .../factory/VerificationItemFactory.kt | 41 +++++++-------- .../timeline/factory/WidgetItemFactory.kt | 19 +++---- .../helper/MessageInformationDataFactory.kt | 22 ++++---- .../TimelineControllerInterceptorHelper.kt | 23 ++++----- .../helper/TimelineEventVisibilityHelper.kt | 12 ++--- .../detail/timeline/item/BaseEventItem.kt | 1 + .../detail/timeline/item/ItemWithEvents.kt | 5 ++ .../detail/timeline/item/ReadReceiptsItem.kt | 2 + 21 files changed, 209 insertions(+), 181 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt diff --git a/vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt index 9c49a5d458..c51573bf21 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt @@ -26,12 +26,15 @@ import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents abstract class TimelineEmptyItem : VectorEpoxyModel(), ItemWithEvents { @EpoxyAttribute lateinit var eventId: String - @EpoxyAttribute var visible: Boolean = false + @EpoxyAttribute var notBlank: Boolean = false + + override fun isVisible() = false override fun bind(holder: Holder) { super.bind(holder) holder.view.updateLayoutParams { - this.height = if (visible) 1 else 0 + // Force height to 1px so scrolling works correctly + this.height = if (notBlank) 1 else 0 } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 972736fb2a..aff94f7157 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -32,9 +32,7 @@ import im.vector.app.core.extensions.localDateTime import im.vector.app.core.extensions.nextOrNull import im.vector.app.core.extensions.prevOrNull import im.vector.app.core.resources.UserPreferencesProvider -import im.vector.app.core.utils.DebouncedClickListener import im.vector.app.features.call.webrtc.WebRtcCallManager -import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.RoomDetailAction import im.vector.app.features.home.room.detail.RoomDetailViewState import im.vector.app.features.home.room.detail.UnreadState @@ -47,6 +45,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.TimelineControlle import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityStateChangedListener +import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem @@ -55,7 +54,6 @@ import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem -import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem_ import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever import im.vector.app.features.media.ImageContentRenderer @@ -337,13 +335,22 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec return } val receiptsByEvents = getReadReceiptsByShownEvent() + val lastSentEventWithoutReadReceipts = searchLastSentEventWithoutReadReceipts(receiptsByEvents) (0 until modelCache.size).forEach { position -> val event = currentSnapshot[position] val nextEvent = currentSnapshot.nextOrNull(position) val prevEvent = currentSnapshot.prevOrNull(position) + val params = TimelineItemFactoryParams( + event = event, + prevEvent = prevEvent, + nextEvent = nextEvent, + highlightedEventId = eventIdToHighlight, + lastSentEventIdWithoutReadReceipts = lastSentEventWithoutReadReceipts, + callback = callback + ) // Should be build if not cached or if model should be refreshed if (modelCache[position] == null || modelCache[position]?.shouldTriggerBuild == true) { - modelCache[position] = buildCacheItem(event, nextEvent, prevEvent) + modelCache[position] = buildCacheItem(params) } val itemCachedData = modelCache[position] ?: return@forEach // Then update with additional models if needed @@ -351,15 +358,13 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec } } - private fun buildCacheItem(event: TimelineEvent, - nextEvent: TimelineEvent?, - prevEvent: TimelineEvent? - ): CacheItemData { + private fun buildCacheItem(params: TimelineItemFactoryParams): CacheItemData { + val event = params.event if (hasReachedInvite && hasUTD) { return CacheItemData(event.localId, event.root.eventId) } - updateUTDStates(event, nextEvent) - val eventModel = timelineItemFactory.create(event, prevEvent, nextEvent, eventIdToHighlight, callback).also { + updateUTDStates(event, params.nextEvent) + val eventModel = timelineItemFactory.create(params).also { it.id(event.localId) it.setOnVisibilityStateChanged(TimelineEventVisibilityStateChangedListener(callback, event)) } @@ -399,13 +404,37 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec ) } + private fun searchLastSentEventWithoutReadReceipts(receiptsByEvent: Map>): String? { + if (timeline?.isLive == false) { + // If timeline is not live we don't want to show SentStatus + return null + } + for (event in currentSnapshot) { + // If there is any RR on the event, we stop searching for Sent event + if (receiptsByEvent[event.eventId]?.isNotEmpty() == true) { + return null + } + // If the event is not shown, we go to the next one + if (!timelineEventVisibilityHelper.shouldShowEvent(event, eventIdToHighlight)) { + continue + } + // If the event is sent by us, we update the holder with the eventId and stop the search + if (event.root.senderId == session.myUserId && event.root.sendState.isSent()) { + return event.eventId + } + } + return null + } + private fun getReadReceiptsByShownEvent(): Map> { val receiptsByEvent = HashMap>() var lastShownEventId: String? = null val itr = currentSnapshot.listIterator(currentSnapshot.size) while (itr.hasPrevious()) { val event = itr.previous() - val currentReadReceipts = ArrayList(event.readReceipts) + val currentReadReceipts = ArrayList(event.readReceipts).filter { + it.user.userId != session.myUserId + } if (timelineEventVisibilityHelper.shouldShowEvent(event, eventIdToHighlight)) { lastShownEventId = event.eventId } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt index 548f7a3b1c..3df9898078 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/CallItemFactory.kt @@ -46,13 +46,11 @@ class CallItemFactory @Inject constructor( private val callManager: WebRtcCallManager ) { - fun create(event: TimelineEvent, - highlight: Boolean, - callback: TimelineEventController.Callback? - ): VectorEpoxyModel<*>? { + fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { + val event = params.event if (event.root.eventId == null) return null val roomId = event.roomId - val informationData = messageInformationDataFactory.create(event, null, null) + val informationData = messageInformationDataFactory.create(params) val callSignalingContent = event.getCallSignallingContent() ?: return null val callId = callSignalingContent.callId ?: return null val call = callManager.getCallById(callId) @@ -68,8 +66,8 @@ class CallItemFactory @Inject constructor( callId = callId, callStatus = CallTileTimelineItem.CallStatus.IN_CALL, callKind = callKind, - callback = callback, - highlight = highlight, + callback = params.callback, + highlight = params.isHighlighted, informationData = informationData, isStillActive = call != null ) @@ -80,8 +78,8 @@ class CallItemFactory @Inject constructor( callId = callId, callStatus = CallTileTimelineItem.CallStatus.INVITED, callKind = callKind, - callback = callback, - highlight = highlight, + callback = params.callback, + highlight = params.isHighlighted, informationData = informationData, isStillActive = call != null ) @@ -92,8 +90,8 @@ class CallItemFactory @Inject constructor( callId = callId, callStatus = CallTileTimelineItem.CallStatus.REJECTED, callKind = callKind, - callback = callback, - highlight = highlight, + callback = params.callback, + highlight = params.isHighlighted, informationData = informationData, isStillActive = false ) @@ -104,8 +102,8 @@ class CallItemFactory @Inject constructor( callId = callId, callStatus = CallTileTimelineItem.CallStatus.ENDED, callKind = callKind, - callback = callback, - highlight = highlight, + callback = params.callback, + highlight = params.isHighlighted, informationData = informationData, isStillActive = false ) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/DefaultItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/DefaultItemFactory.kt index 5f5d3f5156..db7b84ed06 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/DefaultItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/DefaultItemFactory.kt @@ -25,7 +25,6 @@ import im.vector.app.features.home.room.detail.timeline.helper.MessageInformatio import im.vector.app.features.home.room.detail.timeline.item.DefaultItem import im.vector.app.features.home.room.detail.timeline.item.DefaultItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: AvatarSizeProvider, @@ -51,16 +50,14 @@ class DefaultItemFactory @Inject constructor(private val avatarSizeProvider: Ava .attributes(attributes) } - fun create(event: TimelineEvent, - highlight: Boolean, - callback: TimelineEventController.Callback?, - throwable: Throwable? = null): DefaultItem { + fun create(params: TimelineItemFactoryParams, throwable: Throwable? = null): DefaultItem { + val event = params.event val text = if (throwable == null) { stringProvider.getString(R.string.rendering_event_error_type_of_event_not_handled, event.root.getClearType()) } else { stringProvider.getString(R.string.rendering_event_error_exception, event.root.eventId) } - val informationData = informationDataFactory.create(event, null, null) - return create(text, informationData, highlight, callback) + val informationData = informationDataFactory.create(params) + return create(text, informationData, params.isHighlighted, params.callback) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt index b531e08359..82d3dea311 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt @@ -21,7 +21,6 @@ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.DrawableProvider import im.vector.app.core.resources.StringProvider -import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory @@ -33,7 +32,6 @@ import me.gujun.android.span.span import org.matrix.android.sdk.api.session.crypto.MXCryptoError 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.timeline.TimelineEvent import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import javax.inject.Inject @@ -46,11 +44,8 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat private val attributesFactory: MessageItemAttributesFactory, private val vectorPreferences: VectorPreferences) { - fun create(event: TimelineEvent, - prevEvent: TimelineEvent?, - nextEvent: TimelineEvent?, - highlight: Boolean, - callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? { + fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { + val event = params.event event.root.eventId ?: return null return when { @@ -109,14 +104,14 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat } } - val informationData = messageInformationDataFactory.create(event, prevEvent, nextEvent) - val attributes = attributesFactory.create(event.root.content.toModel(), informationData, callback) + val informationData = messageInformationDataFactory.create(params) + val attributes = attributesFactory.create(event.root.content.toModel(), informationData, params.callback) return MessageTextItem_() .leftGuideline(avatarSizeProvider.leftGuideline) - .highlighted(highlight) + .highlighted(params.isHighlighted) .attributes(attributes) .message(spannableStr) - .movementMethod(createLinkMovementMethod(callback)) + .movementMethod(createLinkMovementMethod(params.callback)) } else -> null } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt index 68716a3eba..1d30136f27 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/EncryptionItemFactory.kt @@ -19,7 +19,6 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.room.detail.timeline.MessageColorProvider -import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory @@ -28,7 +27,6 @@ import im.vector.app.features.home.room.detail.timeline.item.StatusTileTimelineI import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent import javax.inject.Inject @@ -41,15 +39,14 @@ class EncryptionItemFactory @Inject constructor( private val avatarSizeProvider: AvatarSizeProvider, private val session: Session) { - fun create(event: TimelineEvent, - highlight: Boolean, - callback: TimelineEventController.Callback?): StatusTileTimelineItem? { + fun create(params: TimelineItemFactoryParams): StatusTileTimelineItem? { + val event = params.event if (!event.root.isStateEvent()) { return null } val algorithm = event.root.getClearContent().toModel()?.algorithm - val informationData = informationDataFactory.create(event, null, null) - val attributes = messageItemAttributesFactory.create(null, informationData, callback) + val informationData = informationDataFactory.create(params) + val attributes = messageItemAttributesFactory.create(null, informationData, params.callback) val isSafeAlgorithm = algorithm == MXCRYPTO_ALGORITHM_MEGOLM val title: String @@ -86,7 +83,7 @@ class EncryptionItemFactory @Inject constructor( readReceiptsCallback = attributes.readReceiptsCallback ) ) - .highlighted(highlight) + .highlighted(params.isHighlighted) .leftGuideline(avatarSizeProvider.leftGuideline) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index 4e4a7fce02..e9340feaca 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -86,7 +86,7 @@ private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper) { eventIdToHighlight: String?, requestModelBuild: () -> Unit, callback: TimelineEventController.Callback?): MergedMembershipEventsItem_? { - val prevSameTypeEvents = timelineEventVisibilityHelper.prevSameTypeEvents(items, currentPosition, 2) + val prevSameTypeEvents = timelineEventVisibilityHelper.prevSameTypeEvents(items, currentPosition, 2, eventIdToHighlight) return if (prevSameTypeEvents.isEmpty()) { null } else { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index e969998613..0f214ffb13 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -85,7 +85,6 @@ import org.matrix.android.sdk.api.session.room.model.message.OPTION_TYPE_BUTTONS import org.matrix.android.sdk.api.session.room.model.message.OPTION_TYPE_POLL import org.matrix.android.sdk.api.session.room.model.message.getFileName import org.matrix.android.sdk.api.session.room.model.message.getFileUrl -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt @@ -118,15 +117,13 @@ class MessageItemFactory @Inject constructor( pillsPostProcessorFactory.create(roomId) } - fun create(event: TimelineEvent, - prevEvent: TimelineEvent?, - nextEvent: TimelineEvent?, - highlight: Boolean, - callback: TimelineEventController.Callback? - ): VectorEpoxyModel<*>? { + fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { + val event = params.event + val highlight = params.isHighlighted + val callback = params.callback event.root.eventId ?: return null roomId = event.roomId - val informationData = messageInformationDataFactory.create(event, prevEvent, nextEvent) + val informationData = messageInformationDataFactory.create(params) if (event.root.isRedacted()) { // message is redacted val attributes = messageItemAttributesFactory.create(null, informationData, callback) @@ -142,7 +139,7 @@ class MessageItemFactory @Inject constructor( || event.isEncrypted() && event.root.content.toModel()?.relatesTo?.type == RelationType.REPLACE ) { // This is an edit event, we should display it when debugging as a notice event - return noticeItemFactory.create(event, highlight, callback) + return noticeItemFactory.create(params) } val attributes = messageItemAttributesFactory.create(messageContent, informationData, callback) @@ -158,7 +155,7 @@ class MessageItemFactory @Inject constructor( is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, attributes) is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) is MessageOptionsContent -> buildOptionsMessageItem(messageContent, informationData, highlight, callback, attributes) - is MessagePollResponseContent -> noticeItemFactory.create(event, highlight, callback) + is MessagePollResponseContent -> noticeItemFactory.create(params) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt index dfabf96199..e757b6b47b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/NoticeItemFactory.kt @@ -17,13 +17,11 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.format.NoticeEventFormatter import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.item.NoticeItem import im.vector.app.features.home.room.detail.timeline.item.NoticeItem_ -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEventFormatter, @@ -31,24 +29,23 @@ class NoticeItemFactory @Inject constructor(private val eventFormatter: NoticeEv private val informationDataFactory: MessageInformationDataFactory, private val avatarSizeProvider: AvatarSizeProvider) { - fun create(event: TimelineEvent, - highlight: Boolean, - callback: TimelineEventController.Callback?): NoticeItem? { + fun create(params: TimelineItemFactoryParams): NoticeItem? { + val event = params.event val formattedText = eventFormatter.format(event) ?: return null - val informationData = informationDataFactory.create(event, null, null) + val informationData = informationDataFactory.create(params) val attributes = NoticeItem.Attributes( avatarRenderer = avatarRenderer, informationData = informationData, noticeText = formattedText, itemLongClickListener = { view -> - callback?.onEventLongClicked(informationData, null, view) ?: false + params.callback?.onEventLongClicked(informationData, null, view) ?: false }, - readReceiptsCallback = callback, - avatarClickListener = { callback?.onAvatarClicked(informationData) } + readReceiptsCallback = params.callback, + avatarClickListener = { params.callback?.onAvatarClicked(informationData) } ) return NoticeItem_() .leftGuideline(avatarSizeProvider.leftGuideline) - .highlighted(highlight) + .highlighted(params.isHighlighted) .attributes(attributes) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt index a3e8541b05..1d015d1bca 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/ReadReceiptsItemFactory.kt @@ -22,27 +22,21 @@ import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem_ -import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.model.ReadReceipt import javax.inject.Inject -class ReadReceiptsItemFactory @Inject constructor(private val session: Session, - private val avatarRenderer: AvatarRenderer) { +class ReadReceiptsItemFactory @Inject constructor(private val avatarRenderer: AvatarRenderer) { fun create(eventId: String, readReceipts: List, callback: TimelineEventController.Callback?): ReadReceiptsItem? { + if (readReceipts.isEmpty()) { + return null + } val readReceiptsData = readReceipts - .asSequence() - .filter { - it.user.userId != session.myUserId - } .map { ReadReceiptData(it.user.userId, it.user.avatarUrl, it.user.displayName, it.originServerTs) } .toList() - if (readReceiptsData.isEmpty()) { - return null - } return ReadReceiptsItem_() .id("read_receipts_$eventId") .eventId(eventId) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt index 31adbdb8a6..382962f98d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/RoomCreateItemFactory.kt @@ -20,13 +20,11 @@ import im.vector.app.R import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.UserPreferencesProvider -import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.item.RoomCreateItem_ import me.gujun.android.span.span import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject class RoomCreateItemFactory @Inject constructor(private val stringProvider: StringProvider, @@ -34,25 +32,26 @@ class RoomCreateItemFactory @Inject constructor(private val stringProvider: Stri private val session: Session, private val noticeItemFactory: NoticeItemFactory) { - fun create(event: TimelineEvent, callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? { + fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { + val event = params.event val createRoomContent = event.root.getClearContent().toModel() ?: return null - val predecessorId = createRoomContent.predecessor?.roomId ?: return defaultRendering(event, callback) + val predecessorId = createRoomContent.predecessor?.roomId ?: return defaultRendering(params) val roomLink = session.permalinkService().createRoomPermalink(predecessorId) ?: return null val text = span { +stringProvider.getString(R.string.room_tombstone_continuation_description) +"\n" span(stringProvider.getString(R.string.room_tombstone_predecessor_link)) { textDecorationLine = "underline" - onClick = { callback?.onRoomCreateLinkClicked(roomLink) } + onClick = { params.callback?.onRoomCreateLinkClicked(roomLink) } } } return RoomCreateItem_() .text(text) } - private fun defaultRendering(event: TimelineEvent, callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? { + private fun defaultRendering(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { return if (userPreferencesProvider.shouldShowHiddenEvents()) { - noticeItemFactory.create(event, false, callback) + noticeItemFactory.create(params) } else { null } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index df4eab0efe..09b17eb901 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -19,8 +19,6 @@ package im.vector.app.features.home.room.detail.timeline.factory import im.vector.app.core.epoxy.TimelineEmptyItem import im.vector.app.core.epoxy.TimelineEmptyItem_ import im.vector.app.core.epoxy.VectorEpoxyModel -import im.vector.app.core.resources.UserPreferencesProvider -import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -41,20 +39,16 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me /** * Reminder: nextEvent is older and prevEvent is newer. */ - fun create(event: TimelineEvent, - prevEvent: TimelineEvent?, - nextEvent: TimelineEvent?, - eventIdToHighlight: String?, - callback: TimelineEventController.Callback?): VectorEpoxyModel<*> { - val highlight = event.root.eventId == eventIdToHighlight + fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*> { + val event = params.event val computedModel = try { - if (!timelineEventVisibilityHelper.shouldShowEvent(event, eventIdToHighlight)) { - return buildEmptyItem(event, prevEvent, eventIdToHighlight) + if (!timelineEventVisibilityHelper.shouldShowEvent(event, params.highlightedEventId)) { + return buildEmptyItem(event, params.prevEvent, params.highlightedEventId) } when (event.root.getClearType()) { - // Message items + // Message itemsX EventType.STICKER, - EventType.MESSAGE -> messageItemFactory.create(event, prevEvent, nextEvent, highlight, callback) + EventType.MESSAGE -> messageItemFactory.create(params) EventType.STATE_ROOM_TOMBSTONE, EventType.STATE_ROOM_NAME, EventType.STATE_ROOM_TOPIC, @@ -78,49 +72,49 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.CALL_SELECT_ANSWER, EventType.CALL_NEGOTIATE, EventType.REACTION, - EventType.STATE_ROOM_POWER_LEVELS -> noticeItemFactory.create(event, highlight, callback) + EventType.STATE_ROOM_POWER_LEVELS -> noticeItemFactory.create(params) EventType.STATE_ROOM_WIDGET_LEGACY, - EventType.STATE_ROOM_WIDGET -> widgetItemFactory.create(event, highlight, callback) - EventType.STATE_ROOM_ENCRYPTION -> encryptionItemFactory.create(event, highlight, callback) + EventType.STATE_ROOM_WIDGET -> widgetItemFactory.create(params) + EventType.STATE_ROOM_ENCRYPTION -> encryptionItemFactory.create(params) // State room create - EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(event, callback) + EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(params) // Calls EventType.CALL_INVITE, EventType.CALL_HANGUP, EventType.CALL_REJECT, - EventType.CALL_ANSWER -> callItemFactory.create(event, highlight, callback) + EventType.CALL_ANSWER -> callItemFactory.create(params) // Crypto EventType.ENCRYPTED -> { if (event.root.isRedacted()) { // Redacted event, let the MessageItemFactory handle it - messageItemFactory.create(event, prevEvent, nextEvent, highlight, callback) + messageItemFactory.create(params) } else { - encryptedItemFactory.create(event, prevEvent, nextEvent, highlight, callback) + encryptedItemFactory.create(params) } } EventType.KEY_VERIFICATION_CANCEL, EventType.KEY_VERIFICATION_DONE -> { - verificationConclusionItemFactory.create(event, highlight, callback) + verificationConclusionItemFactory.create(params) } // Unhandled event types else -> { // Should only happen when shouldShowHiddenEvents() settings is ON Timber.v("Type ${event.root.getClearType()} not handled") - defaultItemFactory.create(event, highlight, callback) + defaultItemFactory.create(params) } } } catch (throwable: Throwable) { Timber.e(throwable, "failed to create message item") - defaultItemFactory.create(event, highlight, callback, throwable) + defaultItemFactory.create(params, throwable) } - return computedModel ?: buildEmptyItem(event, prevEvent, eventIdToHighlight) + return computedModel ?: buildEmptyItem(event, params.prevEvent, params.highlightedEventId) } - private fun buildEmptyItem(timelineEvent: TimelineEvent, prevEvent: TimelineEvent?, eventIdToHighlight: String?): TimelineEmptyItem { - val makesEmptyItemVisible = prevEvent == null || timelineEventVisibilityHelper.shouldShowEvent(prevEvent, eventIdToHighlight) + private fun buildEmptyItem(timelineEvent: TimelineEvent, prevEvent: TimelineEvent?, highlightedEventId: String?): TimelineEmptyItem { + val isNotBlank = prevEvent == null || timelineEventVisibilityHelper.shouldShowEvent(prevEvent, highlightedEventId) return TimelineEmptyItem_() .id(timelineEvent.localId) .eventId(timelineEvent.eventId) - .visible(makesEmptyItemVisible) + .notBlank(isNotBlank) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt new file mode 100644 index 0000000000..dfd9fd2370 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt @@ -0,0 +1,33 @@ +/* + * 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.detail.timeline.factory + +import im.vector.app.features.home.room.detail.timeline.TimelineEventController +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent + +data class TimelineItemFactoryParams( + val event: TimelineEvent, + val prevEvent: TimelineEvent? = null, + val nextEvent: TimelineEvent? = null, + val highlightedEventId: String? = null , + val lastSentEventIdWithoutReadReceipts: String? = null , + val callback: TimelineEventController.Callback? = null +) { + + val isHighlighted: Boolean + get() = highlightedEventId == event.eventId +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt index 960487140d..51951fdc8d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt @@ -20,7 +20,6 @@ import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.features.home.room.detail.timeline.MessageColorProvider -import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory @@ -35,7 +34,6 @@ import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationCancelContent -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import javax.inject.Inject /** @@ -54,37 +52,35 @@ class VerificationItemFactory @Inject constructor( private val session: Session ) { - fun create(event: TimelineEvent, - highlight: Boolean, - callback: TimelineEventController.Callback? - ): VectorEpoxyModel<*>? { + fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { + val event = params.event if (event.root.eventId == null) return null val relContent: MessageRelationContent = event.root.content.toModel() ?: event.root.getClearContent().toModel() - ?: return ignoredConclusion(event, highlight, callback) + ?: return ignoredConclusion(params) - if (relContent.relatesTo?.type != RelationType.REFERENCE) return ignoredConclusion(event, highlight, callback) + if (relContent.relatesTo?.type != RelationType.REFERENCE) return ignoredConclusion(params) val refEventId = relContent.relatesTo?.eventId - ?: return ignoredConclusion(event, highlight, callback) + ?: return ignoredConclusion(params) // If we cannot find the referenced request we do not display the done event val refEvent = session.getRoom(event.root.roomId ?: "")?.getTimeLineEvent(refEventId) - ?: return ignoredConclusion(event, highlight, callback) + ?: return ignoredConclusion(params) // If it's not a request ignore this event // if (refEvent.root.getClearContent().toModel() == null) return ignoredConclusion(event, highlight, callback) - val referenceInformationData = messageInformationDataFactory.create(refEvent, null, null) + val referenceInformationData = messageInformationDataFactory.create(TimelineItemFactoryParams(refEvent)) - val informationData = messageInformationDataFactory.create(event, null, null) - val attributes = messageItemAttributesFactory.create(null, informationData, callback) + val informationData = messageInformationDataFactory.create(params) + val attributes = messageItemAttributesFactory.create(null, informationData,params.callback) when (event.root.getClearType()) { EventType.KEY_VERIFICATION_CANCEL -> { // Is the request referenced is actually really cancelled? val cancelContent = event.root.getClearContent().toModel() - ?: return ignoredConclusion(event, highlight, callback) + ?: return ignoredConclusion(params) when (safeValueOf(cancelContent.code)) { CancelCode.MismatchedCommitment, @@ -107,22 +103,22 @@ class VerificationItemFactory @Inject constructor( readReceiptsCallback = attributes.readReceiptsCallback ) ) - .highlighted(highlight) + .highlighted(params.isHighlighted) .leftGuideline(avatarSizeProvider.leftGuideline) } - else -> return ignoredConclusion(event, highlight, callback) + else -> return ignoredConclusion(params) } } EventType.KEY_VERIFICATION_DONE -> { // Is the request referenced is actually really completed? if (referenceInformationData.referencesInfoData?.verificationStatus != VerificationState.DONE) { - return ignoredConclusion(event, highlight, callback) + return ignoredConclusion(params) } // We only tale the one sent by me if (informationData.sentByMe) { // We only display the done sent by the other user, the done send by me is ignored - return ignoredConclusion(event, highlight, callback) + return ignoredConclusion(params) } return StatusTileTimelineItem_() .attributes( @@ -140,18 +136,15 @@ class VerificationItemFactory @Inject constructor( readReceiptsCallback = attributes.readReceiptsCallback ) ) - .highlighted(highlight) + .highlighted(params.isHighlighted) .leftGuideline(avatarSizeProvider.leftGuideline) } } return null } - private fun ignoredConclusion(event: TimelineEvent, - highlight: Boolean, - callback: TimelineEventController.Callback? - ): VectorEpoxyModel<*>? { - if (userPreferencesProvider.shouldShowHiddenEvents()) return noticeItemFactory.create(event, highlight, callback) + private fun ignoredConclusion(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { + if (userPreferencesProvider.shouldShowHiddenEvents()) return noticeItemFactory.create(params) return null } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt index a6a88a3444..1fc57489a5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/WidgetItemFactory.kt @@ -20,7 +20,6 @@ import im.vector.app.ActiveSessionDataSource import im.vector.app.R import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.resources.StringProvider -import im.vector.app.features.home.room.detail.timeline.TimelineEventController import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory @@ -29,7 +28,6 @@ import im.vector.app.features.home.room.detail.timeline.item.WidgetTileTimelineI import org.matrix.android.sdk.api.extensions.orFalse 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.timeline.TimelineEvent import org.matrix.android.sdk.api.session.widgets.model.WidgetContent import org.matrix.android.sdk.api.session.widgets.model.WidgetType import javax.inject.Inject @@ -47,25 +45,24 @@ class WidgetItemFactory @Inject constructor( private fun Event.isSentByCurrentUser() = senderId != null && senderId == currentUserId - fun create(event: TimelineEvent, - highlight: Boolean, - callback: TimelineEventController.Callback?): VectorEpoxyModel<*>? { + fun create(params: TimelineItemFactoryParams): VectorEpoxyModel<*>? { + val event = params.event val widgetContent: WidgetContent = event.root.getClearContent().toModel() ?: return null val previousWidgetContent: WidgetContent? = event.root.resolvedPrevContent().toModel() return when (WidgetType.fromString(widgetContent.type ?: previousWidgetContent?.type ?: "")) { - WidgetType.Jitsi -> createJitsiItem(event, callback, widgetContent, previousWidgetContent) + WidgetType.Jitsi -> createJitsiItem(params, widgetContent, previousWidgetContent) // There is lot of other widget types we could improve here - else -> noticeItemFactory.create(event, highlight, callback) + else -> noticeItemFactory.create(params) } } - private fun createJitsiItem(timelineEvent: TimelineEvent, - callback: TimelineEventController.Callback?, + private fun createJitsiItem(params: TimelineItemFactoryParams, widgetContent: WidgetContent, previousWidgetContent: WidgetContent?): VectorEpoxyModel<*> { - val informationData = informationDataFactory.create(timelineEvent, null, null) - val attributes = messageItemAttributesFactory.create(null, informationData, callback) + val timelineEvent = params.event + val informationData = informationDataFactory.create(params) + val attributes = messageItemAttributesFactory.create(null, informationData, params.callback) val disambiguatedDisplayName = timelineEvent.senderInfo.disambiguatedDisplayName val message = if (widgetContent.isActive()) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt index abaa2ffa17..14dd311265 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt @@ -19,11 +19,11 @@ package im.vector.app.features.home.room.detail.timeline.helper import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter import im.vector.app.core.extensions.localDateTime +import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData import im.vector.app.features.home.room.detail.timeline.item.PollResponseData import im.vector.app.features.home.room.detail.timeline.item.ReactionInfoData -import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration import im.vector.app.features.settings.VectorPreferences @@ -51,9 +51,10 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses private val dateFormatter: VectorDateFormatter, private val vectorPreferences: VectorPreferences) { - fun create(event: TimelineEvent, prevEvent: TimelineEvent?, nextEvent: TimelineEvent?): MessageInformationData { - // Non nullability has been tested before - val eventId = event.root.eventId!! + fun create(params: TimelineItemFactoryParams): MessageInformationData { + val event = params.event + val nextEvent = params.nextEvent + val eventId = event.eventId val date = event.root.localDateTime() val nextDate = nextEvent?.root?.localDateTime() @@ -76,9 +77,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses val isSentByMe = event.root.senderId == session.myUserId val sendStateDecoration = if (isSentByMe) { getSendStateDecoration( - eventSendState = event.root.sendState, - prevEventSendState = prevEvent?.root?.sendState, - anyReadReceipts = event.readReceipts.any { it.user.userId != session.myUserId }, + event = event, + lastSentEventWithoutReadReceipts = params.lastSentEventIdWithoutReadReceipts, isMedia = event.root.isAttachmentMessage() ) } else { @@ -122,15 +122,15 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses ) } - private fun getSendStateDecoration(eventSendState: SendState, - prevEventSendState: SendState?, - anyReadReceipts: Boolean, + private fun getSendStateDecoration(event: TimelineEvent, + lastSentEventWithoutReadReceipts: String?, isMedia: Boolean): SendStateDecoration { + val eventSendState = event.root.sendState return if (eventSendState.isSending()) { if (isMedia) SendStateDecoration.SENDING_MEDIA else SendStateDecoration.SENDING_NON_MEDIA } else if (eventSendState.hasFailed()) { SendStateDecoration.FAILED - } else if (eventSendState.isSent() && !prevEventSendState?.isSent().orFalse() && !anyReadReceipts) { + } else if (lastSentEventWithoutReadReceipts == event.eventId) { SendStateDecoration.SENT } else { SendStateDecoration.NONE diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt index 392cb0ae57..e8353ff264 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt @@ -19,7 +19,6 @@ package im.vector.app.features.home.room.detail.timeline.helper import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.VisibilityState import im.vector.app.core.epoxy.LoadingItem_ -import im.vector.app.core.epoxy.TimelineEmptyItem import im.vector.app.core.epoxy.TimelineEmptyItem_ import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.features.call.webrtc.WebRtcCallManager @@ -29,7 +28,6 @@ import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineIte import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents import im.vector.app.features.home.room.detail.timeline.item.TimelineReadMarkerItem_ -import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.session.room.timeline.Timeline import kotlin.reflect.KMutableProperty0 @@ -67,30 +65,29 @@ class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMut // Then iterate on models so we have the exact positions in the adapter modelsIterator.forEach { epoxyModel -> - if(epoxyModel !is TimelineEmptyItem){ - atLeastOneVisibleItemSinceLastDaySeparator = true - atLeastOneVisibleItemsBeforeReadMarker = true - } if (epoxyModel is ItemWithEvents) { + if (epoxyModel.isVisible()) { + atLeastOneVisibleItemSinceLastDaySeparator = true + atLeastOneVisibleItemsBeforeReadMarker = true + } epoxyModel.getEventIds().forEach { eventId -> adapterPositionMapping[eventId] = index - if (eventId == firstUnreadEventId && atLeastOneVisibleItemsBeforeReadMarker) { + if (eventId == firstUnreadEventId && atLeastOneVisibleItemsBeforeReadMarker && epoxyModel.canAppendReadMarker()) { modelsIterator.addReadMarkerItem(callback) index++ positionOfReadMarker.set(index) } } } - if(epoxyModel is DaySeparatorItem){ - if(!atLeastOneVisibleItemSinceLastDaySeparator){ + if (epoxyModel is DaySeparatorItem) { + if (!atLeastOneVisibleItemSinceLastDaySeparator) { modelsIterator.remove() return@forEach } atLeastOneVisibleItemSinceLastDaySeparator = false - } - else if (epoxyModel is CallTileTimelineItem) { + } else if (epoxyModel is CallTileTimelineItem) { val hasBeenRemoved = modelsIterator.removeCallItemIfNeeded(epoxyModel, callIds, showHiddenEvents) - if(!hasBeenRemoved){ + if (!hasBeenRemoved) { atLeastOneVisibleItemSinceLastDaySeparator = true } } @@ -126,7 +123,7 @@ class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMut val emptyItem = TimelineEmptyItem_() .id(epoxyModel.id()) .eventId(epoxyModel.attributes.informationData.eventId) - .visible(false) + .notBlank(false) add(emptyItem) } callIds.add(callId) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt index 54d76bc681..39c95a5915 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt @@ -29,7 +29,7 @@ import javax.inject.Inject class TimelineEventVisibilityHelper @Inject constructor(private val userPreferencesProvider: UserPreferencesProvider) { - fun nextSameTypeEvents(timelineEvents: List, index: Int, minSize: Int): List { + fun nextSameTypeEvents(timelineEvents: List, index: Int, minSize: Int, eventIdToHighlight: String?): List { if (index >= timelineEvents.size - 1) { return emptyList() } @@ -51,30 +51,30 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen } else { nextSameDayEvents.subList(0, indexOfFirstDifferentEventType) } - val filteredSameTypeEvents = sameTypeEvents.filter { shouldShowEvent(it)} + val filteredSameTypeEvents = sameTypeEvents.filter { shouldShowEvent(it, eventIdToHighlight)} if (filteredSameTypeEvents.size < minSize) { return emptyList() } return filteredSameTypeEvents } - fun prevSameTypeEvents(timelineEvents: List, index: Int, minSize: Int): List { + fun prevSameTypeEvents(timelineEvents: List, index: Int, minSize: Int, eventIdToHighlight: String?): List { val prevSub = timelineEvents.subList(0, index + 1) return prevSub .reversed() .let { - nextSameTypeEvents(it, 0, minSize) + nextSameTypeEvents(it, 0, minSize, eventIdToHighlight) } .reversed() } - fun shouldShowEvent(timelineEvent: TimelineEvent, highlightEventId: String? = null): Boolean { + fun shouldShowEvent(timelineEvent: TimelineEvent, highlightedEventId: String?): Boolean { // If show hidden events is true we should always display something if (userPreferencesProvider.shouldShowHiddenEvents()) { return true } // We always show highlighted event - if (timelineEvent.eventId == highlightEventId) { + if (timelineEvent.eventId == highlightedEventId) { return true } if (!timelineEvent.isDisplayable()) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt index aae1edbed1..85fcc306f6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt @@ -44,6 +44,7 @@ abstract class BaseEventItem : VectorEpoxyModel @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var dimensionConverter: DimensionConverter + @CallSuper override fun bind(holder: H) { super.bind(holder) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ItemWithEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ItemWithEvents.kt index cf4211bb2c..eeb3826ccd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ItemWithEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ItemWithEvents.kt @@ -22,4 +22,9 @@ interface ItemWithEvents { * Will generally get only one, but it handles the merged items. */ fun getEventIds(): List + + fun canAppendReadMarker(): Boolean = true + + fun isVisible(): Boolean = true + } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt index 84b2662687..b88afb0598 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ReadReceiptsItem.kt @@ -33,6 +33,8 @@ abstract class ReadReceiptsItem : EpoxyModelWithHolder( @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var clickListener: View.OnClickListener + override fun canAppendReadMarker(): Boolean = false + override fun getEventIds(): List = listOf(eventId) override fun bind(holder: Holder) { From 3686d8efadf619f3e63246c6692dae83f4ca66b6 Mon Sep 17 00:00:00 2001 From: Tirifto Date: Mon, 29 Mar 2021 11:49:18 +0000 Subject: [PATCH 131/249] Translated using Weblate (Esperanto) Currently translated at 92.2% (2179 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/eo/ --- vector/src/main/res/values-eo/strings.xml | 90 ++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) 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 From 9543a110a4f78ade95bfb6f744c5cfbd6e55b48f Mon Sep 17 00:00:00 2001 From: XoseM Date: Mon, 29 Mar 2021 05:59:50 +0000 Subject: [PATCH 132/249] Translated using Weblate (Galician) Currently translated at 32.9% (778 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/gl/ --- vector/src/main/res/values-gl/strings.xml | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) 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 From 44db075015c5a4e09847cdacdb283d6aac7db52e Mon Sep 17 00:00:00 2001 From: RainSlide Date: Sun, 28 Mar 2021 18:06:15 +0000 Subject: [PATCH 133/249] Translated using Weblate (Chinese (Simplified)) Currently translated at 98.0% (2316 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- vector/src/main/res/values-zh-rCN/strings.xml | 112 ++++++++++-------- 1 file changed, 63 insertions(+), 49 deletions(-) diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index 365b6827a9..82ec8fc36d 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -145,12 +145,12 @@ 您向 %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 挂件 管理员 审核员 默认 @@ -395,11 +395,11 @@ %d 秒 通话已连接 通话正在连接… - 为了发送或保存附件,${app_name} 需要访问您的图片和视频库。 + 为发送或保存附件,${app_name} 需要权限以访问您的图片和视频库。 \n -\n请在接下来弹出的窗口中授权允许访问,以便应用能够从您的手机发送文件。 - 为了拍照或进行视频通话,${app_name} 需要访问您的相机。 - 为了进行语音通话,${app_name} 需要访问您的麦克风。 +\n请在接下来的弹出窗口中授权允许访问,以便从此设备中发送文件。 + ${app_name} 需要权限来访问您的相机,以拍摄照片或进行视频通话。 + ${app_name} 需要权限以访问您的麦克风来进行语音通话。 您试图访问聊天室 %s。您是否愿意加入这个聊天室? 管理工具 私聊 @@ -582,9 +582,9 @@ " \n \n请在接下来弹出的窗口中授权允许访问。" - ${app_name}需要许可才能访问您的摄像机和麦克风来执行视频通话。 + ${app_name} 需要权限以访问您的摄像机和麦克风来进行视频通话。 \n -\n请在接下来弹出的窗口中授权允许访问。 +\n请在接下来的弹出窗口中授权允许访问,以便进行通话。 对不起。因为权限不足,操作已取消 保存至下载? 移除 @@ -594,8 +594,8 @@ 通话 您将不能撤销这个修改,因为您正在让这个用户和您拥有相同的特权级别。 \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” 联系人 @@ -760,12 +758,12 @@ 黑色主题 通知声音 使用12小时制显示时间戳 - 您需要权限来管理这个聊天室的小部件 - 创建小部件失败 + 您需要权限来管理这个聊天室的挂件 + 创建挂件失败 用 jitsi 创建会议通话 - 您确定要删除这个小部件吗? + 您确定要删除这个挂件吗? - 无法创建小部件。 + 无法创建挂件。 发送请求失败。 特权级别必须是正整数。 您不在这个聊天室。 @@ -859,7 +857,7 @@ %d 个聊天室 - 已启用 %d 个小部件 + 已启用 %d 个挂件 社区名称 @@ -1478,24 +1476,24 @@ %1$s: %2$s %1$s: %2$s %3$s 查看 - 活动小部件 - 小部件 - 载入小部件 - 此小部件添加者: + 活动挂件 + 挂件 + 载入挂件 + 此挂件添加者: 使用它会设置 cookie 并与 %s 分享数据: 使用它会与 %s 分享数据: - 无法载入小部件。 + 无法载入挂件。 \n%s - 重载小部件 + 重载挂件 在浏览器中打开 撤消我的访问权限 您的昵称 您的头像 URL 您的用户 ID 您的主题 - 小部件 ID + 挂件 ID 聊天室 ID - 小部件想使用以下资源: + 挂件想使用以下资源: 允许 阻止全部 使用相机 @@ -1594,7 +1592,7 @@ 服务条款 审核条款 可被其他人发现 - 使用机器人,小部件和贴纸包 + 使用机器人,挂件和贴纸包 已读于 身份服务器 断开身份服务器 @@ -1672,9 +1670,9 @@ 此内容已报告为不合适。 \n \n如果您不希望再看到此用户的更多内容,您可以忽略他们以隐藏他们的消息。 - ${app_name} 需要权限在磁盘上保存您的端对端密钥。 + ${app_name} 需要权限以在磁盘上保存您的端对端密钥。 \n -\n请在下个弹窗中允许访问以便手动导出密钥。 +\n请在接下来的弹出窗口中授权允许访问,以便您手动导出密钥。 目前没有网络连接 忽略用户 全部消息(嘈杂) @@ -2174,8 +2172,8 @@ 请先在设置中接受身份服务器的条款。 为了您的隐私,${app_name} 仅支持发送用户电子邮件和电话号码的哈希值。 关联失败。 - 此标识符无当前关联。 - 您的主服务器 (%1$s) 建议使用 %2$s 作为您的身份服务器 + 目前与此标识符没有关联。 + 您的主服务器(%1$s)建议使用 %2$s 作为您的身份服务器 使用 %1$s 或者,您可以输入任何其他身份服务器 URL 输入身份服务器 URL @@ -2214,14 +2212,14 @@ 您无法访问此消息,因为您的会话不被发送者信任 您无法访问此消息因为发送者有意不发送密钥 正在等待加密历史 - Riot 现在是 Element! - 我们兴奋地宣布我们改名了!您的应用已经是最新的并且您已登录您的账号。 + Riot 现已成为 Element! + 我们很高兴地宣布我们改名了!您的应用已经更新到最新版本,并且您已登录您的账号。 明白了 了解更多 将恢复密钥保存到 - 从我的电话簿添加 - 您的电话簿是空的 - 电话簿 + 从我的通讯录添加 + 您的通讯录是空的 + 通讯录 搜索我的联系人 正在获取您的联系人… 您的通讯录是空的 @@ -2252,13 +2250,13 @@ 会议使用 Jitsi 安全与许可政策。您的会议进行期间当前聊天室内的所有人将看到加入邀请。 您无法呼叫您自己 您无法与自己通话,请等待参与者接受邀请 - 添加小部件失败 - 移除小部件失败 + 添加挂件失败 + 移除挂件失败 成功导入 %1$d/%2$d 个密钥。 管理集成 - 无活动小部件 + 无活动挂件 聊天室已创建,但由于以下原因一些邀请尚未发送: \n \n%s @@ -2454,7 +2452,7 @@ 启用聊天室加密 更改聊天室主地址 更改聊天室头像 - 修改小部件 + 修改挂件 通知每个人 移除其他人发送的消息 封禁用户 @@ -2508,7 +2506,7 @@ 状态事件 发送状态事件 发送自定义事件 - 开发工具 + 开发者工具 视频 验证失败 用户 @@ -2561,4 +2559,20 @@ %s 修改了此聊天室的服务器访问控制列表。 %s 为此聊天室设置了服务器访问控制列表。 您为此聊天室设置了服务器访问控制列表。 + 在 Matrix 上查找联系人 + 用户尚未同意条款。 + 分享此二维码,其他人扫描后即可添加您,并开始聊天。 + 我的二维码 + 分享我的二维码 + 消息类型缺失 + 检查聊天室状态 + 查看已读回执 + 关闭通知 + 无声通知 + 有声通知 + 此聊天室有未发送的草稿 + 有些消息未被发送 + 从文件中导入密钥 + 发生错误,消息未能发送 + 状态键 \ No newline at end of file From 8c4a99390694ef8d040cf5983932e7d38e990590 Mon Sep 17 00:00:00 2001 From: Thibault Martin Date: Mon, 29 Mar 2021 16:25:04 +0000 Subject: [PATCH 134/249] Translated using Weblate (French) Currently translated at 100.0% (2362 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- vector/src/main/res/values-fr/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 7c1496a73e..31eb24371c 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -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é. From 2c7c8b58597c4d474c35f27c93155c7780238560 Mon Sep 17 00:00:00 2001 From: Andrejs Date: Sun, 28 Mar 2021 21:45:44 +0000 Subject: [PATCH 135/249] Translated using Weblate (Latvian) Currently translated at 66.8% (1580 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/ --- vector/src/main/res/values-lv/strings.xml | 616 ++++++++++++++++++++-- 1 file changed, 564 insertions(+), 52 deletions(-) diff --git a/vector/src/main/res/values-lv/strings.xml b/vector/src/main/res/values-lv/strings.xml index b5226f43ce..01646013ff 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. @@ -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) @@ -697,12 +697,12 @@ Lūdzu dod piekļuves atļauju nākamajā uznirstošajā logā, lai būtu iespē 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 From 5d19cfff135d5fecab90e986fd7196aea64d5bb1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 30 Mar 2021 19:59:25 +0200 Subject: [PATCH 136/249] Timeline: fix membership filtering --- .../core/resources/UserPreferencesProvider.kt | 11 ++++-- .../timeline/TimelineEventController.kt | 5 ++- .../factory/MergedHeaderItemFactory.kt | 5 ++- .../TimelineControllerInterceptorHelper.kt | 2 +- .../helper/TimelineEventVisibilityHelper.kt | 34 +++++++++++++++---- .../features/settings/VectorPreferences.kt | 9 ----- .../res/xml/vector_settings_preferences.xml | 12 +++++-- 7 files changed, 53 insertions(+), 25 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/resources/UserPreferencesProvider.kt b/vector/src/main/java/im/vector/app/core/resources/UserPreferencesProvider.kt index f7d7b3864e..04da5394f2 100644 --- a/vector/src/main/java/im/vector/app/core/resources/UserPreferencesProvider.kt +++ b/vector/src/main/java/im/vector/app/core/resources/UserPreferencesProvider.kt @@ -41,7 +41,14 @@ class UserPreferencesProvider @Inject constructor(private val vectorPreferences: vectorPreferences.neverShowLongClickOnRoomHelpAgain() } - fun shouldShowRoomMemberStateEvents(): Boolean { - return vectorPreferences.showRoomMemberStateEvents() + fun shouldShowJoinLeaves(): Boolean { + return vectorPreferences.showJoinLeaveMessages() } + + fun shouldShowAvatarDisplayNameChanges(): Boolean { + return vectorPreferences.showAvatarDisplayNameChangeMessages() + } + + + } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index aff94f7157..6c19f30cf0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -318,7 +318,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec cacheItemData.eventModel } listOf( - cacheItemData?.readReceiptsItem?.takeIf { cacheItemData.mergedHeaderModel == null }, + cacheItemData?.readReceiptsItem?.takeUnless { mergedHeaderItemFactory.isCollapsed(cacheItemData.localId) }, eventModel, cacheItemData?.mergedHeaderModel, cacheItemData?.formattedDayModel?.takeIf { eventModel != null || cacheItemData.mergedHeaderModel != null } @@ -428,6 +428,9 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private fun getReadReceiptsByShownEvent(): Map> { val receiptsByEvent = HashMap>() + if(!userPreferencesProvider.shouldShowReadReceipts()){ + return receiptsByEvent + } var lastShownEventId: String? = null val itr = currentSnapshot.listIterator(currentSnapshot.size) while (itr.hasPrevious()) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt index e9340feaca..cb2a067540 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt @@ -86,12 +86,11 @@ private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper) { eventIdToHighlight: String?, requestModelBuild: () -> Unit, callback: TimelineEventController.Callback?): MergedMembershipEventsItem_? { - val prevSameTypeEvents = timelineEventVisibilityHelper.prevSameTypeEvents(items, currentPosition, 2, eventIdToHighlight) - return if (prevSameTypeEvents.isEmpty()) { + val mergedEvents = timelineEventVisibilityHelper.prevSameTypeEvents(items, currentPosition, 2, eventIdToHighlight) + return if (mergedEvents.isEmpty()) { null } else { var highlighted = false - val mergedEvents = (prevSameTypeEvents + listOf(event)).asReversed() val mergedData = ArrayList(mergedEvents.size) mergedEvents.forEach { mergedEvent -> if (!highlighted && mergedEvent.root.eventId == eventIdToHighlight) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt index e8353ff264..72fdef9f7d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt @@ -72,7 +72,7 @@ class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMut } epoxyModel.getEventIds().forEach { eventId -> adapterPositionMapping[eventId] = index - if (eventId == firstUnreadEventId && atLeastOneVisibleItemsBeforeReadMarker && epoxyModel.canAppendReadMarker()) { + if (epoxyModel.canAppendReadMarker() && eventId == firstUnreadEventId && atLeastOneVisibleItemsBeforeReadMarker) { modelsIterator.addReadMarkerItem(callback) index++ positionOfReadMarker.set(index) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt index 39c95a5915..e55b02fe85 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt @@ -29,12 +29,21 @@ import javax.inject.Inject class TimelineEventVisibilityHelper @Inject constructor(private val userPreferencesProvider: UserPreferencesProvider) { + + /** + * @param timelineEvents the events to search in + * @param index the index to start computing (inclusive) + * @param minSize the minimum number of same type events to have sequentially, otherwise will return an empty list + * @param eventIdToHighlight used to compute visibility + * + * @return a list of timeline events which have sequentially the same type following the next direction. + */ fun nextSameTypeEvents(timelineEvents: List, index: Int, minSize: Int, eventIdToHighlight: String?): List { if (index >= timelineEvents.size - 1) { return emptyList() } val timelineEvent = timelineEvents[index] - val nextSubList = timelineEvents.subList(index + 1, timelineEvents.size) + val nextSubList = timelineEvents.subList(index, timelineEvents.size) val indexOfNextDay = nextSubList.indexOfFirst { val date = it.root.localDateTime() val nextDate = timelineEvent.root.localDateTime() @@ -58,6 +67,14 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen return filteredSameTypeEvents } + /** + * @param timelineEvents the events to search in + * @param index the index to start computing (inclusive) + * @param minSize the minimum number of same type events to have sequentially, otherwise will return an empty list + * @param eventIdToHighlight used to compute visibility + * + * @return a list of timeline events which have sequentially the same type following the prev direction. + */ fun prevSameTypeEvents(timelineEvents: List, index: Int, minSize: Int, eventIdToHighlight: String?): List { val prevSub = timelineEvents.subList(0, index + 1) return prevSub @@ -65,9 +82,13 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen .let { nextSameTypeEvents(it, 0, minSize, eventIdToHighlight) } - .reversed() } + /** + * @param timelineEvent the event to check for visibility + * @param highlightedEventId can be checked to force visibility to true + * @return true if the event should be shown in the timeline. + */ fun shouldShowEvent(timelineEvent: TimelineEvent, highlightedEventId: String?): Boolean { // If show hidden events is true we should always display something if (userPreferencesProvider.shouldShowHiddenEvents()) { @@ -97,7 +118,8 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen } if (root.getClearType() == EventType.STATE_ROOM_MEMBER) { val diff = computeMembershipDiff() - if ((diff.isJoin || diff.isPart) && !userPreferencesProvider.shouldShowRoomMemberStateEvents()) return true + if ((diff.isJoin || diff.isPart) && !userPreferencesProvider.shouldShowJoinLeaves()) return true + if ((diff.isAvatarChange || diff.isDisplaynameChange) && !userPreferencesProvider.shouldShowAvatarDisplayNameChanges()) return true } return false } @@ -110,9 +132,9 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen val isJoin = isMembershipChanged && content?.membership == Membership.JOIN val isPart = isMembershipChanged && content?.membership == Membership.LEAVE && root.stateKey == root.senderId - val isJoinToJoin = !isMembershipChanged && content?.membership == Membership.JOIN - val isDisplaynameChange = isJoinToJoin && content?.displayName != prevContent?.displayName; - val isAvatarChange = isJoinToJoin && content?.avatarUrl !== prevContent?.avatarUrl + val isProfileChanged = !isMembershipChanged && content?.membership == Membership.JOIN + val isDisplaynameChange = isProfileChanged && content?.displayName != prevContent?.displayName; + val isAvatarChange = isProfileChanged && content?.avatarUrl !== prevContent?.avatarUrl return MembershipDiff( isJoin = isJoin, diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index a1151162c8..9b043cfc7c 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -357,15 +357,6 @@ class VectorPreferences @Inject constructor(private val context: Context) { return defaultPrefs.getBoolean(SETTINGS_12_24_TIMESTAMPS_KEY, false) } - /** - * Tells if all room member state events should be shown in the messages list. - * - * @return true all room member state events should be shown in the messages list. - */ - fun showRoomMemberStateEvents(): Boolean { - return defaultPrefs.getBoolean(SETTINGS_SHOW_ROOM_MEMBER_STATE_EVENTS_KEY, true) - } - /** * Tells if the join and leave membership events should be shown in the messages list. * diff --git a/vector/src/main/res/xml/vector_settings_preferences.xml b/vector/src/main/res/xml/vector_settings_preferences.xml index 6a3c60a021..1d39791ad8 100644 --- a/vector/src/main/res/xml/vector_settings_preferences.xml +++ b/vector/src/main/res/xml/vector_settings_preferences.xml @@ -88,9 +88,15 @@ + android:key="SETTINGS_SHOW_JOIN_LEAVE_MESSAGES_KEY" + android:summary="@string/settings_show_join_leave_messages_summary" + android:title="@string/settings_show_join_leave_messages" /> + + Date: Tue, 30 Mar 2021 20:04:26 +0200 Subject: [PATCH 137/249] Timeline: clean code and update CHANGES --- CHANGES.md | 3 ++- .../sdk/internal/database/mapper/TimelineEventMapper.kt | 1 - .../session/room/timeline/DefaultTimelineService.kt | 1 - .../sdk/internal/session/room/timeline/UIEchoManager.kt | 1 - .../vector/app/core/resources/UserPreferencesProvider.kt | 3 --- .../home/room/detail/ScrollOnHighlightedEventCallback.kt | 1 - .../home/room/detail/timeline/TimelineEventController.kt | 2 +- .../room/detail/timeline/factory/TimelineItemFactory.kt | 2 +- .../detail/timeline/factory/TimelineItemFactoryParams.kt | 4 ++-- .../detail/timeline/factory/VerificationItemFactory.kt | 2 +- .../detail/timeline/helper/TimelineDisplayableEvents.kt | 2 -- .../timeline/helper/TimelineEventVisibilityHelper.kt | 9 ++++----- .../home/room/detail/timeline/item/AbsBaseMessageItem.kt | 1 - .../home/room/detail/timeline/item/BaseEventItem.kt | 2 -- .../home/room/detail/timeline/item/BasedMergedItem.kt | 2 -- .../home/room/detail/timeline/item/DefaultItem.kt | 4 +--- .../home/room/detail/timeline/item/ItemWithEvents.kt | 1 - .../detail/timeline/item/MergedMembershipEventsItem.kt | 2 -- .../home/room/detail/timeline/item/NoticeItem.kt | 1 - 19 files changed, 12 insertions(+), 32 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1a095d20be..1020401f91 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,7 @@ Improvements 🙌: - Api interceptor to allow app developers peek responses (#2986) - 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) Bugfix 🐛: - Fix bad theme change for the MainActivity @@ -23,7 +24,7 @@ Translations 🗣: - SDK API changes ⚠️: - - + - Removes filtering options on Timeline. Build 🧱: - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt index 07954650b2..f3bea68c26 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.database.mapper import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.room.model.ReadReceipt 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.internal.database.model.TimelineEventEntity diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index 6b50e2e8de..8de36d0427 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -31,7 +31,6 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.database.RealmSessionProvider -import org.matrix.android.sdk.internal.database.mapper.ReadReceiptsSummaryMapper import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt index 44326ce1ac..4804fbd731 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt @@ -71,7 +71,6 @@ internal class UIEchoManager( } fun onLocalEchoCreated(timelineEvent: TimelineEvent) { - // Manage some ui echos (do it before filter because actual event could be filtered out) when (timelineEvent.root.getClearType()) { EventType.REDACTION -> { diff --git a/vector/src/main/java/im/vector/app/core/resources/UserPreferencesProvider.kt b/vector/src/main/java/im/vector/app/core/resources/UserPreferencesProvider.kt index 04da5394f2..9ab3b9bf45 100644 --- a/vector/src/main/java/im/vector/app/core/resources/UserPreferencesProvider.kt +++ b/vector/src/main/java/im/vector/app/core/resources/UserPreferencesProvider.kt @@ -48,7 +48,4 @@ class UserPreferencesProvider @Inject constructor(private val vectorPreferences: fun shouldShowAvatarDisplayNameChanges(): Boolean { return vectorPreferences.showAvatarDisplayNameChangeMessages() } - - - } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnHighlightedEventCallback.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnHighlightedEventCallback.kt index 72f827e469..5d3a91f18d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnHighlightedEventCallback.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/ScrollOnHighlightedEventCallback.kt @@ -20,7 +20,6 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import im.vector.app.core.platform.DefaultListUpdateCallback import im.vector.app.features.home.room.detail.timeline.TimelineEventController -import org.matrix.android.sdk.api.session.room.timeline.Timeline import timber.log.Timber import java.util.concurrent.atomic.AtomicReference diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt index 6c19f30cf0..b67527c24c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt @@ -428,7 +428,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec private fun getReadReceiptsByShownEvent(): Map> { val receiptsByEvent = HashMap>() - if(!userPreferencesProvider.shouldShowReadReceipts()){ + if (!userPreferencesProvider.shouldShowReadReceipts()) { return receiptsByEvent } var lastShownEventId: String? = null diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index 09b17eb901..47bc60eb75 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -60,7 +60,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me EventType.STATE_ROOM_HISTORY_VISIBILITY, EventType.STATE_ROOM_SERVER_ACL, EventType.STATE_ROOM_GUEST_ACCESS, - EventType.REDACTION , + EventType.REDACTION, EventType.STATE_ROOM_ALIASES, EventType.KEY_VERIFICATION_ACCEPT, EventType.KEY_VERIFICATION_START, diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt index dfd9fd2370..caf7204a01 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt @@ -23,8 +23,8 @@ data class TimelineItemFactoryParams( val event: TimelineEvent, val prevEvent: TimelineEvent? = null, val nextEvent: TimelineEvent? = null, - val highlightedEventId: String? = null , - val lastSentEventIdWithoutReadReceipts: String? = null , + val highlightedEventId: String? = null, + val lastSentEventIdWithoutReadReceipts: String? = null, val callback: TimelineEventController.Callback? = null ) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt index 51951fdc8d..e972ddcab5 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt @@ -74,7 +74,7 @@ class VerificationItemFactory @Inject constructor( val referenceInformationData = messageInformationDataFactory.create(TimelineItemFactoryParams(refEvent)) val informationData = messageInformationDataFactory.create(params) - val attributes = messageItemAttributesFactory.create(null, informationData,params.callback) + val attributes = messageItemAttributesFactory.create(null, informationData, params.callback) when (event.root.getClearType()) { EventType.KEY_VERIFICATION_CANCEL -> { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt index a597fb966e..053b804a82 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt @@ -16,7 +16,6 @@ package im.vector.app.features.home.room.detail.timeline.helper -import im.vector.app.core.extensions.localDateTime import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -53,7 +52,6 @@ object TimelineDisplayableEvents { EventType.KEY_VERIFICATION_DONE, EventType.KEY_VERIFICATION_CANCEL ) - } fun TimelineEvent.canBeMerged(): Boolean { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt index e55b02fe85..580d7d18cf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt @@ -29,14 +29,13 @@ import javax.inject.Inject class TimelineEventVisibilityHelper @Inject constructor(private val userPreferencesProvider: UserPreferencesProvider) { - /** * @param timelineEvents the events to search in * @param index the index to start computing (inclusive) * @param minSize the minimum number of same type events to have sequentially, otherwise will return an empty list * @param eventIdToHighlight used to compute visibility * - * @return a list of timeline events which have sequentially the same type following the next direction. + * @return a list of timeline events which have sequentially the same type following the next direction. */ fun nextSameTypeEvents(timelineEvents: List, index: Int, minSize: Int, eventIdToHighlight: String?): List { if (index >= timelineEvents.size - 1) { @@ -60,7 +59,7 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen } else { nextSameDayEvents.subList(0, indexOfFirstDifferentEventType) } - val filteredSameTypeEvents = sameTypeEvents.filter { shouldShowEvent(it, eventIdToHighlight)} + val filteredSameTypeEvents = sameTypeEvents.filter { shouldShowEvent(it, eventIdToHighlight) } if (filteredSameTypeEvents.size < minSize) { return emptyList() } @@ -128,12 +127,12 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen val content = root.getClearContent().toModel() val prevContent = root.resolvedPrevContent().toModel() - val isMembershipChanged = content?.membership != prevContent?.membership; + val isMembershipChanged = content?.membership != prevContent?.membership val isJoin = isMembershipChanged && content?.membership == Membership.JOIN val isPart = isMembershipChanged && content?.membership == Membership.LEAVE && root.stateKey == root.senderId val isProfileChanged = !isMembershipChanged && content?.membership == Membership.JOIN - val isDisplaynameChange = isProfileChanged && content?.displayName != prevContent?.displayName; + val isDisplaynameChange = isProfileChanged && content?.displayName != prevContent?.displayName val isAvatarChange = isProfileChanged && content?.avatarUrl !== prevContent?.avatarUrl return MembershipDiff( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt index a5f3f7c547..39c04af089 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt @@ -24,7 +24,6 @@ import androidx.annotation.IdRes import androidx.core.view.isVisible import im.vector.app.R import im.vector.app.core.ui.views.ShieldImageView -import im.vector.app.core.utils.DebouncedClickListener import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.MessageColorProvider import im.vector.app.features.home.room.detail.timeline.TimelineEventController diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt index 85fcc306f6..7d539f9df7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BaseEventItem.kt @@ -26,7 +26,6 @@ import im.vector.app.R import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.platform.CheckableView -import im.vector.app.core.ui.views.ReadReceiptsView import im.vector.app.core.utils.DimensionConverter /** @@ -44,7 +43,6 @@ abstract class BaseEventItem : VectorEpoxyModel @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var dimensionConverter: DimensionConverter - @CallSuper override fun bind(holder: H) { super.bind(holder) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt index 8a49bd6803..1c56a0809e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/BasedMergedItem.kt @@ -19,10 +19,8 @@ package im.vector.app.features.home.room.detail.timeline.item import android.view.View import android.widget.TextView import androidx.annotation.IdRes -import androidx.core.view.isVisible import im.vector.app.R import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.home.room.detail.timeline.TimelineEventController import org.matrix.android.sdk.api.util.MatrixItem abstract class BasedMergedItem : BaseEventItem() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultItem.kt index 580a56bf05..e6c6e1d372 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/DefaultItem.kt @@ -22,9 +22,7 @@ import android.widget.TextView import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R -import im.vector.app.core.utils.DebouncedClickListener import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.home.room.detail.timeline.TimelineEventController @EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo) abstract class DefaultItem : BaseEventItem() { @@ -59,7 +57,7 @@ abstract class DefaultItem : BaseEventItem() { val avatarRenderer: AvatarRenderer, val informationData: MessageInformationData, val text: CharSequence, - val itemLongClickListener: View.OnLongClickListener? = null, + val itemLongClickListener: View.OnLongClickListener? = null ) companion object { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ItemWithEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ItemWithEvents.kt index eeb3826ccd..050cba0d56 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ItemWithEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/ItemWithEvents.kt @@ -26,5 +26,4 @@ interface ItemWithEvents { fun canAppendReadMarker(): Boolean = true fun isVisible(): Boolean = true - } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt index 70a6864a1f..a52ddf8336 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedMembershipEventsItem.kt @@ -21,12 +21,10 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.core.view.children -import androidx.core.view.isVisible import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyModelClass import im.vector.app.R import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.home.room.detail.timeline.TimelineEventController @EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo) abstract class MergedMembershipEventsItem : BasedMergedItem() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt index b733d0ec32..4876e8e500 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/NoticeItem.kt @@ -25,7 +25,6 @@ import im.vector.app.R import im.vector.app.core.epoxy.ClickListener import im.vector.app.core.epoxy.onClick import im.vector.app.core.ui.views.ShieldImageView -import im.vector.app.core.utils.DebouncedClickListener import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.timeline.TimelineEventController import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel From 6226938c6f64a626f7e43cad7ccd472859c901b6 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Tue, 30 Mar 2021 19:13:16 +0100 Subject: [PATCH 138/249] Missing file Signed-off-by: Dominic Fischer --- .../features/settings/troubleshoot/TestPushFromPushGateway.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt index 6cf7d68561..6ce944d214 100644 --- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt @@ -53,7 +53,7 @@ class TestPushFromPushGateway @Inject constructor(private val context: AppCompat status = TestStatus.RUNNING }, { - description = if (failure is PushGatewayFailure.PusherRejected) { + description = if (it is PushGatewayFailure.PusherRejected) { stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_failed) } else { errorFormatter.toHumanReadable(it) @@ -70,6 +70,6 @@ class TestPushFromPushGateway @Inject constructor(private val context: AppCompat } override fun cancel() { - job?.cancel() + action?.cancel() } } From 145c9d2e4417522ea8175f5b02525db12a23bdd4 Mon Sep 17 00:00:00 2001 From: Dominic Fischer Date: Tue, 30 Mar 2021 19:18:40 +0100 Subject: [PATCH 139/249] Formatting Signed-off-by: Dominic Fischer --- .../crypto/quads/SharedSecureStorageViewModel.kt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt index 573a95a7dd..11a30b304e 100644 --- a/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModel.kt @@ -129,13 +129,13 @@ class SharedSecureStorageViewModel @AssistedInject constructor( override fun handle(action: SharedSecureStorageAction) = withState { when (action) { is SharedSecureStorageAction.TogglePasswordVisibility -> handleTogglePasswordVisibility() - is SharedSecureStorageAction.Cancel -> handleCancel() - is SharedSecureStorageAction.SubmitPassphrase -> handleSubmitPassphrase(action) - SharedSecureStorageAction.UseKey -> handleUseKey() - is SharedSecureStorageAction.SubmitKey -> handleSubmitKey(action) - SharedSecureStorageAction.Back -> handleBack() - SharedSecureStorageAction.ForgotResetAll -> handleResetAll() - SharedSecureStorageAction.DoResetAll -> handleDoResetAll() + is SharedSecureStorageAction.Cancel -> handleCancel() + is SharedSecureStorageAction.SubmitPassphrase -> handleSubmitPassphrase(action) + SharedSecureStorageAction.UseKey -> handleUseKey() + is SharedSecureStorageAction.SubmitKey -> handleSubmitKey(action) + SharedSecureStorageAction.Back -> handleBack() + SharedSecureStorageAction.ForgotResetAll -> handleResetAll() + SharedSecureStorageAction.DoResetAll -> handleDoResetAll() }.exhaustive } From 57df510ecb09e6fdb1508fd0d204517553533065 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Mar 2021 06:58:20 +0000 Subject: [PATCH 140/249] Bump stetho from 1.5.1 to 1.6.0 Bumps [stetho](https://github.com/facebook/stetho) from 1.5.1 to 1.6.0. - [Release notes](https://github.com/facebook/stetho/releases) - [Changelog](https://github.com/facebook/stetho/blob/master/CHANGELOG.md) - [Commits](https://github.com/facebook/stetho/compare/v1.5.1...v1.6.0) Signed-off-by: dependabot[bot] --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index e3a7b090e3..b7ec0f76f4 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -339,7 +339,7 @@ dependencies { implementation 'com.jakewharton.timber:timber:4.7.1' // Debug - implementation 'com.facebook.stetho:stetho:1.5.1' + implementation 'com.facebook.stetho:stetho:1.6.0' // Phone number https://github.com/google/libphonenumber implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.20' From e4e13aaa9790f154b6032b05e62907901d72d4a4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 31 Mar 2021 09:25:35 +0200 Subject: [PATCH 141/249] ktlint and cleanup --- .../troubleshoot/TestPushFromPushGateway.kt | 34 ++++++++++--------- .../vector/app/core/pushers/PushersManager.kt | 2 -- .../home/room/detail/RoomDetailViewModel.kt | 1 - .../NotificationBroadcastReceiver.kt | 1 - 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt index 6ce944d214..015754145f 100644 --- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt +++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt @@ -24,8 +24,10 @@ import im.vector.app.core.pushers.PushersManager import im.vector.app.core.resources.StringProvider import im.vector.app.features.settings.troubleshoot.TroubleshootTest import im.vector.app.push.fcm.FcmHelper +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.pushers.PushGatewayFailure -import kotlinx.coroutines.* import javax.inject.Inject /** @@ -45,22 +47,22 @@ class TestPushFromPushGateway @Inject constructor(private val context: AppCompat return } action = GlobalScope.launch { - runCatching { pushersManager.testPush(fcmToken) } - .fold( - { - // Wait for the push to be received - description = stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_waiting_for_push) - status = TestStatus.RUNNING - }, - { - description = if (it is PushGatewayFailure.PusherRejected) { - stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_failed) - } else { - errorFormatter.toHumanReadable(it) + status = runCatching { pushersManager.testPush(fcmToken) } + .fold( + { + // Wait for the push to be received + description = stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_waiting_for_push) + TestStatus.RUNNING + }, + { + description = if (it is PushGatewayFailure.PusherRejected) { + stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_failed) + } else { + errorFormatter.toHumanReadable(it) + } + TestStatus.FAILED } - status = TestStatus.FAILED - } - ) + ) } } diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt index dda8b70b08..5896122393 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/PushersManager.kt @@ -21,8 +21,6 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.resources.AppNameProvider import im.vector.app.core.resources.LocaleProvider import im.vector.app.core.resources.StringProvider -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.util.Cancelable import java.util.UUID import javax.inject.Inject import kotlin.math.abs diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index e859d001b7..a26d51d593 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -65,7 +65,6 @@ import org.commonmark.parser.Parser import org.commonmark.renderer.html.HtmlRenderer import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixPatterns -import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.raw.RawService diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt index 7b0707ad61..7125c22342 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationBroadcastReceiver.kt @@ -25,7 +25,6 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.vectorComponent import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.read.ReadService From f49df59e1013b6d536ee4d0b613909f659d04952 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 31 Mar 2021 10:33:12 +0200 Subject: [PATCH 142/249] Convert Room API to suspend (#24499) --- .../sdk/session/search/SearchMessagesTest.kt | 59 +++++++------------ .../android/sdk/api/session/room/Room.kt | 5 +- .../sdk/internal/session/room/DefaultRoom.kt | 29 ++++----- .../sdk/internal/session/room/RoomFactory.kt | 3 - .../room/detail/search/SearchViewModel.kt | 34 +++++------ 5 files changed, 50 insertions(+), 80 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt index cadb83ca00..312516f486 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt @@ -17,30 +17,27 @@ package org.matrix.android.sdk.session.search import org.junit.Assert.assertTrue -import org.junit.Assert.fail import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings -import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper -import org.matrix.android.sdk.common.TestConstants import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) class SearchMessagesTest : InstrumentedTest { - private val MESSAGE = "Lorem ipsum dolor sit amet" + companion object { + private const val MESSAGE = "Lorem ipsum dolor sit amet" + } private val commonTestHelper = CommonTestHelper(context()) private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) @@ -115,7 +112,7 @@ class SearchMessagesTest : InstrumentedTest { 2) run { - var lock = CountDownLatch(1) + val lock = CountDownLatch(1) val eventListener = commonTestHelper.createEventListener(lock) { snapshot -> snapshot.count { it.root.content.toModel()?.body?.startsWith(MESSAGE).orFalse() } == 2 @@ -124,37 +121,25 @@ class SearchMessagesTest : InstrumentedTest { aliceTimeline.addListener(eventListener) commonTestHelper.await(lock) - lock = CountDownLatch(1) - roomFromAlicePOV - .search( - searchTerm = "lore", - limit = 10, - includeProfile = true, - afterLimit = 0, - beforeLimit = 10, - orderByRecent = true, - nextBatch = null, - callback = object : MatrixCallback { - override fun onSuccess(data: SearchResult) { - super.onSuccess(data) - assertTrue(data.results?.size == 2) - assertTrue( - data.results - ?.all { - (it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse() - }.orFalse() - ) - lock.countDown() - } + val data = commonTestHelper.runBlockingTest { + roomFromAlicePOV.search( + searchTerm = "lore", + limit = 10, + includeProfile = true, + afterLimit = 0, + beforeLimit = 10, + orderByRecent = true, + nextBatch = null + ) + } - override fun onFailure(failure: Throwable) { - super.onFailure(failure) - fail(failure.localizedMessage) - lock.countDown() - } - } - ) - lock.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS) + assertTrue(data.results?.size == 2) + assertTrue( + data.results + ?.all { + (it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse() + }.orFalse() + ) aliceTimeline.removeAllListeners() cryptoTestData.cleanUp(commonTestHelper) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt index cb6690b5c5..06c13d4256 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt @@ -86,12 +86,11 @@ interface Room : * @param includeProfile requests that the server returns the historic profile information for the users that sent the events that were returned. * @param callback Callback to get the search result */ - fun search(searchTerm: String, + suspend fun search(searchTerm: String, nextBatch: String?, orderByRecent: Boolean, limit: Int, beforeLimit: Int, afterLimit: Int, - includeProfile: Boolean, - callback: MatrixCallback): Cancelable + includeProfile: Boolean): SearchResult } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt index 8e817ec31a..1d8eb6c95e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.room import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.Room @@ -37,14 +36,11 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.typing.TypingService import org.matrix.android.sdk.api.session.room.uploads.UploadsService import org.matrix.android.sdk.api.session.search.SearchResult -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.session.room.state.SendStateTask import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.search.SearchTask -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.awaitCallback import java.security.InvalidParameterException import javax.inject.Inject @@ -66,7 +62,6 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, private val relationService: RelationService, private val roomMembersService: MembershipService, private val roomPushRuleService: RoomPushRuleService, - private val taskExecutor: TaskExecutor, private val sendStateTask: SendStateTask, private val searchTask: SearchTask) : Room, @@ -133,16 +128,15 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, } } - override fun search(searchTerm: String, - nextBatch: String?, - orderByRecent: Boolean, - limit: Int, - beforeLimit: Int, - afterLimit: Int, - includeProfile: Boolean, - callback: MatrixCallback): Cancelable { - return searchTask - .configureWith(SearchTask.Params( + override suspend fun search(searchTerm: String, + nextBatch: String?, + orderByRecent: Boolean, + limit: Int, + beforeLimit: Int, + afterLimit: Int, + includeProfile: Boolean): SearchResult { + return searchTask.execute( + SearchTask.Params( searchTerm = searchTerm, roomId = roomId, nextBatch = nextBatch, @@ -151,8 +145,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, beforeLimit = beforeLimit, afterLimit = afterLimit, includeProfile = includeProfile - )) { - this.callback = callback - }.executeBy(taskExecutor) + ) + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt index 63370a1ad8..90640b4700 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt @@ -36,7 +36,6 @@ import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimelineServ import org.matrix.android.sdk.internal.session.room.typing.DefaultTypingService import org.matrix.android.sdk.internal.session.room.uploads.DefaultUploadsService import org.matrix.android.sdk.internal.session.search.SearchTask -import org.matrix.android.sdk.internal.task.TaskExecutor import javax.inject.Inject internal interface RoomFactory { @@ -60,7 +59,6 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService: private val relationServiceFactory: DefaultRelationService.Factory, private val membershipServiceFactory: DefaultMembershipService.Factory, private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory, - private val taskExecutor: TaskExecutor, private val sendStateTask: SendStateTask, private val searchTask: SearchTask) : RoomFactory { @@ -84,7 +82,6 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService: relationService = relationServiceFactory.create(roomId), roomMembersService = membershipServiceFactory.create(roomId), roomPushRuleService = roomPushRuleServiceFactory.create(roomId), - taskExecutor = taskExecutor, sendStateTask = sendStateTask, searchTask = searchTask ) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt index cb93cf95d2..fb3abf002e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/search/SearchViewModel.kt @@ -24,26 +24,24 @@ import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success 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.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.Job import kotlinx.coroutines.launch import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.search.SearchResult -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.internal.util.awaitCallback class SearchViewModel @AssistedInject constructor( @Assisted private val initialState: SearchViewState, session: Session ) : VectorViewModel(initialState) { - private var room: Room? = session.getRoom(initialState.roomId) + private val room = session.getRoom(initialState.roomId) - private var currentTask: Cancelable? = null + private var currentTask: Job? = null private var nextBatch: String? = null @@ -92,6 +90,7 @@ class SearchViewModel @AssistedInject constructor( } private fun startSearching(isNextBatch: Boolean) = withState { state -> + if (room == null) return@withState if (state.searchTerm == null) return@withState // There is no batch to retrieve @@ -108,20 +107,17 @@ class SearchViewModel @AssistedInject constructor( currentTask?.cancel() - viewModelScope.launch { + currentTask = viewModelScope.launch { try { - val result = awaitCallback { - currentTask = room?.search( - searchTerm = state.searchTerm, - nextBatch = nextBatch, - orderByRecent = true, - beforeLimit = 0, - afterLimit = 0, - includeProfile = true, - limit = 20, - callback = it - ) - } + val result = room.search( + searchTerm = state.searchTerm, + nextBatch = nextBatch, + orderByRecent = true, + beforeLimit = 0, + afterLimit = 0, + includeProfile = true, + limit = 20 + ) onSearchResultSuccess(result) } catch (failure: Throwable) { if (failure is Failure.Cancelled) return@launch From 0a0c8cde34fd5fced070fcf0af4e210ec89865c1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 31 Mar 2021 10:51:12 +0200 Subject: [PATCH 143/249] Cleanup the test --- .../sdk/session/search/SearchMessagesTest.kt | 136 +++++++----------- 1 file changed, 53 insertions(+), 83 deletions(-) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt index 312516f486..1baf490dfc 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt @@ -27,7 +27,9 @@ import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings +import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.CryptoTestData import org.matrix.android.sdk.common.CryptoTestHelper import java.util.concurrent.CountDownLatch @@ -44,107 +46,75 @@ class SearchMessagesTest : InstrumentedTest { @Test fun sendTextMessageAndSearchPartOfItUsingSession() { - val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) - val aliceSession = cryptoTestData.firstSession - val aliceRoomId = cryptoTestData.roomId - aliceSession.cryptoService().setWarnOnUnknownDevices(false) - val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! - val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(10)) - aliceTimeline.start() - - commonTestHelper.sendTextMessage( - roomFromAlicePOV, - MESSAGE, - 2) - - run { - val lock = CountDownLatch(1) - - val eventListener = commonTestHelper.createEventListener(lock) { snapshot -> - snapshot.count { it.root.content.toModel()?.body?.startsWith(MESSAGE).orFalse() } == 2 - } - - aliceTimeline.addListener(eventListener) - commonTestHelper.await(lock) - - val data = commonTestHelper.runBlockingTest { - aliceSession - .searchService() - .search( - searchTerm = "lore", - limit = 10, - includeProfile = true, - afterLimit = 0, - beforeLimit = 10, - orderByRecent = true, - nextBatch = null, - roomId = aliceRoomId - ) - } - assertTrue(data.results?.size == 2) - assertTrue( - data.results - ?.all { - (it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse() - }.orFalse() - ) - - aliceTimeline.removeAllListeners() - cryptoTestData.cleanUp(commonTestHelper) + doTest { cryptoTestData -> + cryptoTestData.firstSession + .searchService() + .search( + searchTerm = "lore", + limit = 10, + includeProfile = true, + afterLimit = 0, + beforeLimit = 10, + orderByRecent = true, + nextBatch = null, + roomId = cryptoTestData.roomId + ) } - - aliceSession.startSync(true) } @Test fun sendTextMessageAndSearchPartOfItUsingRoom() { - val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) + doTest { cryptoTestData -> + cryptoTestData.firstSession + .getRoom(cryptoTestData.roomId)!! + .search( + searchTerm = "lore", + limit = 10, + includeProfile = true, + afterLimit = 0, + beforeLimit = 10, + orderByRecent = true, + nextBatch = null + ) + } + } + + private fun doTest(block: suspend (CryptoTestData) -> SearchResult) { + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false) val aliceSession = cryptoTestData.firstSession val aliceRoomId = cryptoTestData.roomId - aliceSession.cryptoService().setWarnOnUnknownDevices(false) val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(10)) aliceTimeline.start() + val lock = CountDownLatch(1) + + val eventListener = commonTestHelper.createEventListener(lock) { snapshot -> + snapshot.count { it.root.content.toModel()?.body?.startsWith(MESSAGE).orFalse() } == 2 + } + + aliceTimeline.addListener(eventListener) + commonTestHelper.sendTextMessage( roomFromAlicePOV, MESSAGE, 2) - run { - val lock = CountDownLatch(1) + commonTestHelper.await(lock) - val eventListener = commonTestHelper.createEventListener(lock) { snapshot -> - snapshot.count { it.root.content.toModel()?.body?.startsWith(MESSAGE).orFalse() } == 2 - } - - aliceTimeline.addListener(eventListener) - commonTestHelper.await(lock) - - val data = commonTestHelper.runBlockingTest { - roomFromAlicePOV.search( - searchTerm = "lore", - limit = 10, - includeProfile = true, - afterLimit = 0, - beforeLimit = 10, - orderByRecent = true, - nextBatch = null - ) - } - - assertTrue(data.results?.size == 2) - assertTrue( - data.results - ?.all { - (it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse() - }.orFalse() - ) - - aliceTimeline.removeAllListeners() - cryptoTestData.cleanUp(commonTestHelper) + val data = commonTestHelper.runBlockingTest { + block.invoke(cryptoTestData) } - aliceSession.startSync(true) + assertTrue(data.results?.size == 2) + assertTrue( + data.results + ?.all { + (it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse() + }.orFalse() + ) + + aliceTimeline.removeAllListeners() + cryptoTestData.cleanUp(commonTestHelper) } } From bf65531268d8fb72d48af36114d9712a6ae5eb79 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 31 Mar 2021 11:31:02 +0200 Subject: [PATCH 144/249] Remove dead code --- .../app/features/widgets/WidgetAPICallback.kt | 36 ------------------- .../features/widgets/WidgetPostAPIHandler.kt | 4 --- 2 files changed, 40 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/widgets/WidgetAPICallback.kt diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetAPICallback.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetAPICallback.kt deleted file mode 100644 index ad17a5ae87..0000000000 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetAPICallback.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2020 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.widgets - -import im.vector.app.R -import im.vector.app.core.resources.StringProvider -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.session.widgets.WidgetPostAPIMediator -import org.matrix.android.sdk.api.util.JsonDict - -class WidgetAPICallback(private val postAPIMediator: WidgetPostAPIMediator, - private val eventData: JsonDict, - private val stringProvider: StringProvider) : MatrixCallback { - - override fun onFailure(failure: Throwable) { - postAPIMediator.sendError(stringProvider.getString(R.string.widget_integration_failed_to_send_request), eventData) - } - - override fun onSuccess(data: Any) { - postAPIMediator.sendSuccess(eventData) - } -} diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt index f5b2c7e4a8..9fa04aabbb 100644 --- a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt +++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt @@ -464,10 +464,6 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo return false } - private fun createWidgetAPICallback(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict): WidgetAPICallback { - return WidgetAPICallback(widgetPostAPIMediator, eventData, stringProvider) - } - private fun launchWidgetAPIAction(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict, block: suspend () -> Unit): Job { return GlobalScope.launch { kotlin.runCatching { From af9fa44e8c4e690b6aec236fff58fac07eb039f3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 31 Mar 2021 11:36:02 +0200 Subject: [PATCH 145/249] Remove dead code --- .../sdk/api/util/MatrixCallbackDelegate.kt | 24 ------------- .../crypto/model/MXQueuedEncryption.kt | 34 ------------------- .../android/sdk/internal/extensions/Try.kt | 5 --- 3 files changed, 63 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixCallbackDelegate.kt delete mode 100755 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXQueuedEncryption.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixCallbackDelegate.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixCallbackDelegate.kt deleted file mode 100644 index 63d37f409f..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixCallbackDelegate.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2020 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.util - -import org.matrix.android.sdk.api.MatrixCallback - -/** - * Simple MatrixCallback implementation which delegate its calls to another callback - */ -open class MatrixCallbackDelegate(private val callback: MatrixCallback) : MatrixCallback by callback diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXQueuedEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXQueuedEncryption.kt deleted file mode 100755 index fe6b3a74bb..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXQueuedEncryption.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2020 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.crypto.model - -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.session.events.model.Content - -class MXQueuedEncryption { - - /** - * The data to encrypt. - */ - var eventContent: Content? = null - var eventType: String? = null - - /** - * the asynchronous callback - */ - var apiCallback: MatrixCallback? = null -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Try.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Try.kt index 8786321464..2ce0534b49 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Try.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Try.kt @@ -21,7 +21,6 @@ import arrow.core.Success import arrow.core.Try import arrow.core.TryOf import arrow.core.fix -import org.matrix.android.sdk.api.MatrixCallback inline fun TryOf.onError(f: (Throwable) -> Unit): Try = fix() .fold( @@ -32,10 +31,6 @@ inline fun TryOf.onError(f: (Throwable) -> Unit): Try = fix() { Success(it) } ) -fun Try.foldToCallback(callback: MatrixCallback): Unit = fold( - { callback.onFailure(it) }, - { callback.onSuccess(it) }) - /** * Same as doOnNext for Observables */ From f4c84d599fc133c8499a5ac8291b515da384fbf9 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 31 Mar 2021 11:39:06 +0200 Subject: [PATCH 146/249] Add changelog for the next release. --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 1a095d20be..0559c31b90 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,7 +23,7 @@ Translations 🗣: - SDK API changes ⚠️: - - + - Several Services have been migrated to coroutines (#2449) Build 🧱: - From cb96a9059e0290bc35349d0a917e396bf82975fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Mar 2021 13:48:01 +0000 Subject: [PATCH 147/249] Bump stetho-okhttp3 from 1.5.1 to 1.6.0 Bumps [stetho-okhttp3](https://github.com/facebook/stetho) from 1.5.1 to 1.6.0. - [Release notes](https://github.com/facebook/stetho/releases) - [Changelog](https://github.com/facebook/stetho/blob/master/CHANGELOG.md) - [Commits](https://github.com/facebook/stetho/compare/v1.5.1...v1.6.0) Signed-off-by: dependabot[bot] --- matrix-sdk-android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index d74678ae55..0ca605dc8b 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -163,7 +163,7 @@ dependencies { // Logging implementation 'com.jakewharton.timber:timber:4.7.1' - implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1' + implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0' // Phone number https://github.com/google/libphonenumber implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.20' From 741100e1aa7bc4770012e75cec4408269bc7ede6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 31 Mar 2021 17:49:04 +0200 Subject: [PATCH 148/249] static val --- .../room/detail/timeline/factory/TimelineItemFactoryParams.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt index caf7204a01..f92cd2800a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt @@ -27,7 +27,5 @@ data class TimelineItemFactoryParams( val lastSentEventIdWithoutReadReceipts: String? = null, val callback: TimelineEventController.Callback? = null ) { - - val isHighlighted: Boolean - get() = highlightedEventId == event.eventId + val isHighlighted = highlightedEventId == event.eventId } From 42f6adf2e2a1f8e6cbdd5f2bfa05138537d35708 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 31 Mar 2021 17:55:25 +0200 Subject: [PATCH 149/249] ktlint --- .../main/java/org/matrix/android/sdk/api/session/room/Room.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt index 06c13d4256..257c83564e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.api.session.room import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.room.alias.AliasService import org.matrix.android.sdk.api.session.room.call.RoomCallService import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService @@ -35,7 +34,6 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.typing.TypingService import org.matrix.android.sdk.api.session.room.uploads.UploadsService import org.matrix.android.sdk.api.session.search.SearchResult -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional /** From 6a91c2b355a6e621e91c8831ae204c27360811aa Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 31 Mar 2021 14:21:47 +0200 Subject: [PATCH 150/249] Migrate Retrofit interface to coroutine calls --- CHANGES.md | 1 + .../android/sdk/internal/auth/AuthAPI.kt | 27 ++- .../auth/DefaultAuthenticationService.kt | 25 ++- .../auth/IsValidClientServerApiTask.kt | 5 +- .../internal/auth/login/DefaultLoginWizard.kt | 18 +- .../internal/auth/login/DirectLoginTask.kt | 20 +-- .../registration/RegisterAddThreePidTask.kt | 2 +- .../auth/registration/RegisterTask.kt | 2 +- .../auth/registration/ValidateCodeTask.kt | 2 +- .../sdk/internal/crypto/api/CryptoApi.kt | 33 ++-- .../crypto/keysbackup/api/RoomKeysApi.kt | 55 +++--- .../tasks/CreateKeysBackupVersionTask.kt | 2 +- .../keysbackup/tasks/DeleteBackupTask.kt | 2 +- .../tasks/DeleteRoomSessionDataTask.kt | 2 +- .../tasks/DeleteRoomSessionsDataTask.kt | 2 +- .../tasks/DeleteSessionsDataTask.kt | 2 +- .../tasks/GetKeysBackupLastVersionTask.kt | 2 +- .../tasks/GetKeysBackupVersionTask.kt | 2 +- .../tasks/GetRoomSessionDataTask.kt | 2 +- .../tasks/GetRoomSessionsDataTask.kt | 2 +- .../keysbackup/tasks/GetSessionsDataTask.kt | 2 +- .../tasks/StoreRoomSessionDataTask.kt | 2 +- .../tasks/StoreRoomSessionsDataTask.kt | 2 +- .../keysbackup/tasks/StoreSessionsDataTask.kt | 2 +- .../tasks/UpdateKeysBackupVersionTask.kt | 2 +- .../ClaimOneTimeKeysForUsersDeviceTask.kt | 5 +- .../internal/crypto/tasks/DeleteDeviceTask.kt | 4 +- .../crypto/tasks/DownloadKeysForUsersTask.kt | 6 +- .../crypto/tasks/GetDeviceInfoTask.kt | 2 +- .../internal/crypto/tasks/GetDevicesTask.kt | 2 +- .../crypto/tasks/GetKeyChangesTask.kt | 2 +- .../internal/crypto/tasks/RedactEventTask.kt | 7 +- .../internal/crypto/tasks/SendEventTask.kt | 7 +- .../internal/crypto/tasks/SendToDeviceTask.kt | 23 ++- .../tasks/SendVerificationMessageTask.kt | 7 +- .../crypto/tasks/SetDeviceNameTask.kt | 2 +- .../internal/crypto/tasks/UploadKeysTask.kt | 2 +- .../crypto/tasks/UploadSignaturesTask.kt | 14 +- .../crypto/tasks/UploadSigningKeysTask.kt | 5 +- .../sdk/internal/federation/FederationAPI.kt | 3 +- .../federation/GetFederationVersionTask.kt | 4 +- .../android/sdk/internal/network/Request.kt | 41 +++-- .../internal/network/RetrofitExtensions.kt | 8 + .../android/sdk/internal/raw/GetUrlTask.kt | 5 +- .../matrix/android/sdk/internal/raw/RawAPI.kt | 3 +- .../internal/session/account/AccountAPI.kt | 5 +- .../session/account/ChangePasswordTask.kt | 8 +- .../session/account/DeactivateAccountTask.kt | 4 +- .../session/call/GetTurnServerTask.kt | 2 +- .../sdk/internal/session/call/VoipApi.kt | 3 +- .../session/directory/DirectoryAPI.kt | 15 +- .../sdk/internal/session/filter/FilterApi.kt | 9 +- .../internal/session/filter/SaveFilterTask.kt | 4 +- .../session/group/GetGroupDataTask.kt | 12 +- .../sdk/internal/session/group/GroupAPI.kt | 7 +- .../session/homeserver/CapabilitiesAPI.kt | 7 +- .../GetHomeServerCapabilitiesTask.kt | 12 +- .../session/homeserver/HomeServerPinger.kt | 4 +- .../internal/session/identity/IdentityAPI.kt | 17 +- .../session/identity/IdentityAuthAPI.kt | 7 +- .../identity/IdentityBulkLookupTask.kt | 4 +- .../identity/IdentityDisconnectTask.kt | 4 +- .../session/identity/IdentityPingTask.kt | 8 +- .../session/identity/IdentityRegisterTask.kt | 2 +- .../IdentityRequestTokenForBindingTask.kt | 5 +- .../IdentitySubmitTokenForBindingTask.kt | 5 +- .../session/identity/IdentityTaskHelper.kt | 5 +- .../session/media/GetPreviewUrlTask.kt | 4 +- .../session/media/GetRawPreviewUrlTask.kt | 2 +- .../sdk/internal/session/media/MediaAPI.kt | 5 +- .../session/openid/GetOpenIdTokenTask.kt | 2 +- .../sdk/internal/session/openid/OpenIdAPI.kt | 5 +- .../session/profile/AddThreePidTask.kt | 32 ++-- .../session/profile/BindThreePidsTask.kt | 4 +- .../session/profile/DeleteThreePidTask.kt | 12 +- .../profile/FinalizeAddingThreePidTask.kt | 4 +- .../session/profile/GetProfileInfoTask.kt | 2 +- .../internal/session/profile/ProfileAPI.kt | 29 ++-- .../profile/RefreshUserThreePidsTask.kt | 4 +- .../session/profile/SetAvatarUrlTask.kt | 8 +- .../session/profile/SetDisplayNameTask.kt | 8 +- .../session/profile/UnbindThreePidsTask.kt | 4 +- .../session/profile/ValidateSmsCodeTask.kt | 5 +- .../session/pushers/AddHttpPusherWorker.kt | 4 +- .../session/pushers/AddPushRuleTask.kt | 2 +- .../session/pushers/GetPushRulesTask.kt | 5 +- .../session/pushers/GetPushersTask.kt | 4 +- .../internal/session/pushers/PushRulesApi.kt | 29 ++-- .../internal/session/pushers/PushersAPI.kt | 5 +- .../session/pushers/RemovePushRuleTask.kt | 2 +- .../session/pushers/RemovePusherTask.kt | 4 +- .../pushers/UpdatePushRuleActionsTask.kt | 8 +- .../pushers/UpdatePushRuleEnableStatusTask.kt | 2 +- .../session/pushers/gateway/PushGatewayAPI.kt | 3 +- .../pushers/gateway/PushGatewayNotifyTask.kt | 4 +- .../sdk/internal/session/room/RoomAPI.kt | 157 +++++++++--------- .../session/room/alias/AddRoomAliasTask.kt | 4 +- .../session/room/alias/DeleteRoomAliasTask.kt | 4 +- .../room/alias/GetRoomIdByAliasTask.kt | 4 +- .../room/alias/GetRoomLocalAliasesTask.kt | 4 +- .../alias/RoomAliasAvailabilityChecker.kt | 4 +- .../session/room/create/CreateRoomTask.kt | 4 +- .../room/directory/GetPublicRoomTask.kt | 2 +- .../GetRoomDirectoryVisibilityTask.kt | 5 +- .../SetRoomDirectoryVisibilityTask.kt | 4 +- .../room/membership/LoadRoomMembersTask.kt | 4 +- .../membership/admin/MembershipAdminTask.kt | 4 +- .../room/membership/joining/InviteTask.kt | 14 +- .../room/membership/joining/JoinRoomTask.kt | 5 +- .../room/membership/leaving/LeaveRoomTask.kt | 4 +- .../membership/threepid/InviteThreePidTask.kt | 2 +- .../room/peeking/ResolveRoomStateTask.kt | 2 +- .../session/room/read/SetReadMarkersTask.kt | 8 +- .../room/relation/FetchEditHistoryTask.kt | 4 +- .../room/relation/SendRelationWorker.kt | 5 +- .../room/reporting/ReportContentTask.kt | 2 +- .../session/room/send/RedactEventWorker.kt | 4 +- .../session/room/state/SendStateTask.kt | 2 +- .../session/room/tags/AddTagToRoomTask.kt | 4 +- .../room/tags/DeleteTagFromRoomTask.kt | 4 +- .../timeline/FetchTokenAndPaginateTask.kt | 4 +- .../room/timeline/GetContextOfEventTask.kt | 4 +- .../session/room/timeline/GetEventTask.kt | 2 +- .../session/room/timeline/PaginationTask.kt | 8 +- .../session/room/typing/SendTypingTask.kt | 4 +- .../session/room/uploads/GetUploadsTask.kt | 5 +- .../sdk/internal/session/search/SearchAPI.kt | 5 +- .../sdk/internal/session/search/SearchTask.kt | 38 ++--- .../session/signout/SignInAgainTask.kt | 5 +- .../internal/session/signout/SignOutAPI.kt | 5 +- .../internal/session/signout/SignOutTask.kt | 4 +- .../sdk/internal/session/sync/SyncAPI.kt | 10 +- .../sdk/internal/session/sync/SyncTask.kt | 9 +- .../session/terms/DefaultTermsService.kt | 14 +- .../sdk/internal/session/terms/TermsAPI.kt | 9 +- .../thirdparty/GetThirdPartyProtocolsTask.kt | 2 +- .../thirdparty/GetThirdPartyUserTask.kt | 2 +- .../session/thirdparty/ThirdPartyAPI.kt | 6 +- .../internal/session/user/SearchUserAPI.kt | 3 +- .../user/accountdata/AccountDataAPI.kt | 7 +- .../accountdata/UpdateIgnoredUserIdsTask.kt | 4 +- .../accountdata/UpdateUserAccountDataTask.kt | 2 +- .../session/user/model/SearchUserTask.kt | 4 +- .../session/widgets/CreateWidgetTask.kt | 4 +- .../internal/session/widgets/WidgetsAPI.kt | 9 +- .../widgets/token/GetScalarTokenTask.kt | 9 +- .../internal/wellknown/GetWellknownTask.kt | 12 +- .../sdk/internal/wellknown/WellKnownAPI.kt | 3 +- 148 files changed, 576 insertions(+), 598 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ed549c918f..a97430eff9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -35,6 +35,7 @@ Test: 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/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..2ce5c67a94 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 @@ -29,7 +29,6 @@ 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 @@ -45,26 +44,26 @@ 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 /** * Add 3Pid during registration @@ -72,22 +71,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 +96,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/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/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/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..fe6868968b 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,19 @@ internal class DefaultSendToDeviceTask @Inject constructor( messages = params.contentMap.map ) - return executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.sendToDevice( - params.eventType, - params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(), - sendToDeviceBody - ) - isRetryable = true - maxRetryCount = 3 - } + return executeRequest( + globalErrorReceiver, + { + 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..bac886cafc 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,11 +35,14 @@ 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, + { cryptoApi.uploadSignatures(params.signatures) }, + { + isRetryable = true + maxRetryCount = 10 + } + ) 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/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..9f3000ce18 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 @@ -21,15 +21,28 @@ import kotlinx.coroutines.delay import org.matrix.android.sdk.api.failure.Failure 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 +// To use when there is no init block to provide internal suspend inline fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, - block: Request.() -> Unit) = Request(globalErrorReceiver).apply(block).execute() + noinline requestBlock: suspend () -> DATA): DATA { + return executeRequest(globalErrorReceiver, requestBlock, {}) +} -internal class Request(private val globalErrorReceiver: GlobalErrorReceiver?) { +internal suspend inline fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, + noinline requestBlock: suspend () -> DATA, + initBlock: Request.() -> Unit): DATA { + return Request(globalErrorReceiver, requestBlock) + .apply(initBlock) + .execute() +} + +internal class Request( + private val globalErrorReceiver: GlobalErrorReceiver?, + private val requestBlock: suspend () -> DATA +) { var isRetryable = false var initialDelay: Long = 100L @@ -37,20 +50,22 @@ internal class Request(private val globalErrorReceiver: GlobalErrorR 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) + try { + requestBlock() + } catch (exception: Throwable) { + throw when (exception) { + is KotlinNullPointerException -> IllegalStateException("The request returned a null body") + is HttpException -> exception.toFailure(globalErrorReceiver) + else -> exception + } } } 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("?")}") + // Log some details about the request which has failed. This is less useful than before... + // Timber.e("Exception when executing request ${apiCall.request().method} ${apiCall.request().url.toString().substringBefore("?")}") + Timber.e("Exception when executing request") // Check if this is a certificateException CertUtil.getCertificateException(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/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/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/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/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/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index b065a30fc9..ad699af6f4 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,8 @@ 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) /** * Invite a user to the given room. @@ -162,8 +161,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 +170,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 +181,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 +194,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 +215,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 +229,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 +243,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 +254,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 +264,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 +274,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 +284,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 +298,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 +313,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 +340,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/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..fb3182d28b 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 @@ -75,8 +75,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) { 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/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..0f141c344a 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, + { roomAPI.invite(params.roomId, body) }, + { + isRetryable = true + maxRetryCount = 3 + } + ) } } 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..9fd4d95d9e 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 @@ -24,7 +24,6 @@ import org.matrix.android.sdk.internal.database.model.RoomEntityFields import org.matrix.android.sdk.internal.di.SessionDatabase 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 @@ -54,8 +53,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) 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..23a3f2d36c 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 @@ -96,10 +96,10 @@ 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, + { roomAPI.sendReadMarker(params.roomId, markers) }, + { isRetryable = true } + ) } } 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/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/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/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..a9b8683a28 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 @@ -37,7 +37,7 @@ internal class GetEventTask @Inject constructor( override suspend fun execute(params: Params): Event { return executeRequest(globalErrorReceiver) { - apiCall = roomAPI.getEvent(params.roomId, params.eventId) + roomAPI.getEvent(params.roomId, params.eventId) } } } 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..b1ce5c1f26 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,10 +42,10 @@ 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, + { roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter) }, + { isRetryable = true } + ) 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/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/terms/DefaultTermsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt index 41914cc799..73545205d1 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 @@ -24,7 +24,6 @@ import org.matrix.android.sdk.api.session.terms.TermsService import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate import org.matrix.android.sdk.internal.network.NetworkConstants import org.matrix.android.sdk.internal.network.RetrofitFactory -import org.matrix.android.sdk.internal.network.executeRequest 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 @@ -35,6 +34,7 @@ import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccoun import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.ensureTrailingSlash import okhttp3.OkHttpClient +import org.matrix.android.sdk.internal.network.executeRequest import javax.inject.Inject internal class DefaultTermsService @Inject constructor( @@ -50,10 +50,10 @@ internal class DefaultTermsService @Inject constructor( ) : TermsService { override suspend fun getTerms(serviceType: TermsService.ServiceType, baseUrl: String): GetTermsResponse { - return withContext(coroutineDispatchers.main) { + return withContext(coroutineDispatchers.io) { val url = buildUrl(baseUrl, serviceType) - val termsResponse = executeRequest(null) { - apiCall = termsAPI.getTerms("${url}terms") + val termsResponse = executeRequest(null) { + termsAPI.getTerms("${url}terms") } GetTermsResponse(termsResponse, getAlreadyAcceptedTermUrlsFromAccountData()) } @@ -63,12 +63,12 @@ internal class DefaultTermsService @Inject constructor( baseUrl: String, agreedUrls: List, token: String?) { - withContext(coroutineDispatchers.main) { + withContext(coroutineDispatchers.io) { val url = buildUrl(baseUrl, serviceType) val tokenToUse = token?.takeIf { it.isNotEmpty() } ?: getToken(baseUrl) - executeRequest(null) { - apiCall = termsAPI.agreeToTerms("${url}terms", AcceptTermsBody(agreedUrls), "Bearer $tokenToUse") + executeRequest(null) { + termsAPI.agreeToTerms("${url}terms", AcceptTermsBody(agreedUrls), "Bearer $tokenToUse") } // client SHOULD update this account data section adding any the URLs 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 } From cf868f885f1225b54d8c7f5ec1997fc59b0807fe Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Mar 2021 10:26:37 +0200 Subject: [PATCH 151/249] Room summary paged initial commit --- .../android/sdk/api/query/QueryStringValue.kt | 1 + .../sdk/api/query/RoomTagQueryFilter.kt | 23 ++ .../sdk/api/session/room/RoomService.kt | 8 + .../session/room/RoomSummaryQueryParams.kt | 17 +- .../summary/RoomAggregateNotificationCount.kt | 26 ++ .../database/RealmSessionStoreMigration.kt | 47 +++- .../SessionRealmConfigurationFactory.kt | 1 + .../database/mapper/RoomSummaryMapper.kt | 2 +- .../database/model/RoomSummaryEntity.kt | 230 +++++++++++++--- .../session/room/DefaultRoomService.kt | 19 ++ .../room/summary/RoomSummaryDataSource.kt | 96 ++++++- .../room/summary/RoomSummaryUpdater.kt | 10 +- .../internal/session/sync/RoomTagHandler.kt | 10 +- .../java/im/vector/app/AppStateHandler.kt | 2 +- .../app/core/platform/VectorBaseFragment.kt | 6 + .../features/home/room/list/RoomListAction.kt | 2 +- .../room/list/RoomListFooterController.kt | 54 ++++ .../home/room/list/RoomListFragment.kt | 249 +++++++++++------- .../home/room/list/RoomListListener.kt | 27 ++ .../home/room/list/RoomListViewModel.kt | 224 ++++++++++------ .../room/list/RoomListViewModelFactory.kt | 6 +- .../home/room/list/RoomListViewState.kt | 47 +--- .../home/room/list/RoomSummaryController.kt | 170 ------------ .../home/room/list/RoomSummaryItemFactory.kt | 4 +- .../room/list/RoomSummaryPagedController.kt | 71 +++++ .../home/room/list/SectionHeaderAdapter.kt | 100 +++++++ 26 files changed, 1007 insertions(+), 445 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomTagQueryFilter.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/RoomListFooterController.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt delete mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryController.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/SectionHeaderAdapter.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt index 8f83beface..99a86e185b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt @@ -33,3 +33,4 @@ sealed class QueryStringValue { INSENSITIVE } } + 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/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index 5f02b77a1e..4814805dd8 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,6 +17,7 @@ package org.matrix.android.sdk.api.session.room import androidx.lifecycle.LiveData +import androidx.paging.PagedList import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState @@ -26,7 +27,9 @@ 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.util.Cancelable import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.internal.session.room.UpdatableFilterLivePageResult import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription +import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount /** * This interface defines methods to get rooms. It's implemented at the session level. @@ -178,4 +181,9 @@ interface RoomService { * This call will try to gather some information on this room, but it could fail and get nothing more */ fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback) + + fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData> + + fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount + fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): UpdatableFilterLivePageResult } 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..a57514023b 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,12 +17,19 @@ package org.matrix.android.sdk.api.session.room import org.matrix.android.sdk.api.query.QueryStringValue +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 { return RoomSummaryQueryParams.Builder().apply(init).build() } +enum class RoomCategoryFilter { + ONLY_DM, + ONLY_ROOMS, + ALL +} + /** * This class can be used to filter room summaries to use with: * [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService] @@ -31,7 +38,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 +49,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/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..2aa122c84e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt @@ -0,0 +1,26 @@ +/* + * 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 org.matrix.android.sdk.api.session.room.summary + +data class RoomAggregateNotificationCount( + val notificationCount: Int, + val highlightCount: Int +) { + fun totalCount() = notificationCount + highlightCount + + fun isHighlight() = highlightCount > 0 +} 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/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt index 244fe3432a..770ef46080 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt @@ -72,6 +72,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor( .allowWritesOnUiThread(true) .modules(SessionRealmModule()) .schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION) +// .deleteRealmIfMigrationNeeded() .migration(migration) .build() 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/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt index 37696c9082..6007ae504a 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,221 @@ 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 +import timber.log.Timber 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(): RealmList = 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.onEach { it.deleteFromRealm() } + newTags.forEach { newTag -> + if (tags.indexOfFirst { it.tagName == newTag.first } == -1) { + // we must add it + tags.add( + RoomTagEntity(newTag.first, newTag.second) + ) + } + } + + isFavourite = newTags.indexOfFirst { it.first == RoomTag.ROOM_TAG_FAVOURITE } != -1 + isLowPriority = newTags.indexOfFirst { it.first == RoomTag.ROOM_TAG_LOW_PRIORITY } != -1 + isServerNotice = newTags.indexOfFirst { it.first == RoomTag.ROOM_TAG_SERVER_NOTICE } != -1 + } + + @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.toSet() != aliases.toSet()) { + Timber.w("VAL: aliases updated") + aliases.clear() + aliases.addAll(newAliases) + } + } + + // this is required for querying + var flatAliases: String = "" + set(value) { + if (value != field) field = value + } + + 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 +244,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/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index 383dd876d3..5feb9c9d5a 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,6 +18,7 @@ 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.MatrixCallback import org.matrix.android.sdk.api.session.events.model.Event @@ -45,6 +46,7 @@ import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomT import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask +import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask import org.matrix.android.sdk.internal.task.TaskExecutor @@ -96,6 +98,18 @@ internal class DefaultRoomService @Inject constructor( return roomSummaryDataSource.getRoomSummariesLive(queryParams) } + override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams) : LiveData> { + return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams) + } + + override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams) : UpdatableFilterLivePageResult { + return roomSummaryDataSource.getFilteredPagedRoomSummariesLive(queryParams) + } + + override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount { + return roomSummaryDataSource.getNotificationCountForRooms(queryParams) + } + override fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List { return roomSummaryDataSource.getBreadcrumbs(queryParams) } @@ -178,3 +192,8 @@ internal class DefaultRoomService @Inject constructor( .executeBy(taskExecutor) } } + +interface UpdatableFilterLivePageResult { + val livePagedList: LiveData> + fun updateQuery(queryParams: RoomSummaryQueryParams) +} 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..a806123237 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,17 @@ 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.session.room.RoomCategoryFilter import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams 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 @@ -31,9 +38,8 @@ import org.matrix.android.sdk.internal.database.query.findByAlias 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.session.room.UpdatableFilterLivePageResult 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,71 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat .sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX) } + fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): 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, + PagedList.Config.Builder() + .setPageSize(10) + .setInitialLoadSizeHint(20) + .setEnablePlaceholders(false) + .setPrefetchDistance(10) + .build()) + ) + } + + fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): 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, + PagedList.Config.Builder() + .setPageSize(10) + .setInitialLoadSizeHint(20) + .setEnablePlaceholders(false) + .setPrefetchDistance(10) + .build()) + ) + + return object : UpdatableFilterLivePageResult { + override val livePagedList: LiveData> + get() = 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 +176,27 @@ 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.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 { lp -> + query.equalTo(RoomSummaryEntityFields.IS_SERVER_NOTICE, lp) + } + } 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..69e1332dab 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,6 +98,11 @@ 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) @@ -112,8 +117,9 @@ internal class RoomSummaryUpdater @Inject constructor( val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel()?.aliases .orEmpty() - roomSummaryEntity.aliases.clear() - roomSummaryEntity.aliases.addAll(roomAliases) +// roomSummaryEntity.aliases.clear() +// roomSummaryEntity.aliases.addAll(roomAliases) + roomSummaryEntity.updateAliases(roomAliases) roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|") roomSummaryEntity.isEncrypted = encryptionEvent != null roomSummaryEntity.encryptionEventTs = encryptionEvent?.originServerTs 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/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index 1e92f7bc67..d2b59e5513 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -52,7 +52,7 @@ class AppStateHandler @Inject constructor( @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun entersForeground() { - observeRoomsAndGroup() +// observeRoomsAndGroup() } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) 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..e3a3b7b29c 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,11 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScre Timber.i("onResume Fragment ${javaClass.simpleName}") } + override fun onPause() { + super.onPause() + Timber.i("onPause Fragment ${javaClass.simpleName}") + } + @CallSuper override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -150,6 +155,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScre } override fun onDestroy() { + Timber.i("onDestroy Fragment ${javaClass.simpleName}") uiDisposables.dispose() super.onDestroy() } 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..0d70f99b3a 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,7 +22,7 @@ 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: RoomListViewModel.RoomsSection) : RoomListAction() data class AcceptInvitation(val roomSummary: RoomSummary) : RoomListAction() data class RejectInvitation(val roomSummary: RoomSummary) : RoomListAction() data class FilterWith(val filter: String) : 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..0e7e35654c 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 @@ -27,12 +27,10 @@ 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 +42,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 +52,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 +64,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 { @@ -87,6 +86,20 @@ class RoomListFragment @Inject constructor( private var hasUnreadRooms = false + data class SectionKey( + val name: String, + val isExpanded: Boolean + ) + + data class SectionAdapterInfo( + var section: SectionKey, + val headerHeaderAdapter: SectionHeaderAdapter, + val contentAdapter: RoomSummaryPagedController + ) + + private val adapterInfosList = mutableListOf() + private var concatAdapter: ConcatAdapter? = null + override fun getMenuRes() = R.menu.room_list override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -112,10 +125,10 @@ class RoomListFragment @Inject constructor( sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) roomListViewModel.observeViewEvents { when (it) { - is RoomListViewEvents.Loading -> showLoading(it.message) - is RoomListViewEvents.Failure -> showFailure(it.throwable) + is RoomListViewEvents.Loading -> showLoading(it.message) + is RoomListViewEvents.Failure -> showFailure(it.throwable) is RoomListViewEvents.SelectRoom -> handleSelectRoom(it) - is RoomListViewEvents.Done -> Unit + is RoomListViewEvents.Done -> Unit }.exhaustive } @@ -127,15 +140,49 @@ class RoomListFragment @Inject constructor( .disposeOnDestroyView() } + 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.section.copy(isExpanded = isRoomSectionExpanded) + ) + } + } + override fun showFailure(throwable: Throwable) { showErrorInSnackbar(throwable) } override fun onDestroyView() { - roomController.removeModelBuildListener(modelBuildListener) + adapterInfosList.onEach { it.contentAdapter.removeModelBuildListener(modelBuildListener) } + adapterInfosList.clear() +// roomController.removeModelBuildListener(modelBuildListener) modelBuildListener = null views.roomListView.cleanup() - roomController.listener = null +// controllers.onEach { +// it.listener = null +// // concatAdapter.removeAdapter(it.adapter) +// } +// controllers.clear() +// roomController.listener = null +// favRoomController.listener = null stateRestorer.clear() views.createChatFabMenu.listener = null super.onDestroyView() @@ -148,8 +195,8 @@ class RoomListFragment @Inject constructor( private fun setupCreateRoomButton() { when (roomListParams.displayMode) { RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.isVisible = true - RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.isVisible = true - RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.isVisible = true + RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.isVisible = true + RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.isVisible = true else -> Unit // No button in this mode } @@ -204,21 +251,68 @@ class RoomListFragment @Inject constructor( stateRestorer = LayoutManagerStateRestorer(layoutManager).register() views.roomListView.layoutManager = layoutManager views.roomListView.itemAnimator = RoomListAnimator() - views.roomListView.setRecycledViewPool(sharedViewPool) +// 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() + val hasOnlyOneSection = roomListViewModel.sections.size == 1 + roomListViewModel.sections.forEach { section -> + + val sectionAdapter = SectionHeaderAdapter { + roomListViewModel.handle(RoomListAction.ToggleSection(section)) + }.also { + it.updateSection(SectionHeaderAdapter.SectionViewModel( + section.sectionName + )) + } + + val contentAdapter = pagedControllerFactory.createRoomSummaryPagedController().also { + section.livePages.observe(viewLifecycleOwner) { pl -> + it.submitList(pl) + sectionAdapter.updateSection(sectionAdapter.section.copy(isHidden = pl.isEmpty() || hasOnlyOneSection)) + checkEmptyState() + } + section.notificationCount.observe(viewLifecycleOwner) { counts -> + sectionAdapter.updateSection(sectionAdapter.section.copy( + notificationCount = counts.totalCount(), + isHighlighted = counts.isHighlight() + )) + } + section.isExpanded.observe(viewLifecycleOwner) { _ -> + refreshCollapseStates() + } + it.listener = this + } + adapterInfosList.add( + SectionAdapterInfo( + SectionKey( + name = section.sectionName, + isExpanded = section.isExpanded.value.orTrue() + ), + sectionAdapter, + contentAdapter + ) + ) + concatAdapter.addAdapter(sectionAdapter) + concatAdapter.addAdapter(contentAdapter.adapter) + } + + // Add the footer controller + this.footerController.listener = this + concatAdapter.addAdapter(footerController.adapter) + + this.concatAdapter = concatAdapter + views.roomListView.adapter = concatAdapter } private val showFabRunnable = Runnable { if (isAdded) { when (roomListParams.displayMode) { RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.show() - RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.show() - RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.show() + RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.show() + RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.show() else -> Unit } } @@ -226,28 +320,28 @@ class RoomListFragment @Inject constructor( private fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) { when (quickAction) { - is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> { + is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> { roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.ALL_MESSAGES_NOISY)) } - is RoomListQuickActionsSharedAction.NotificationsAll -> { + is RoomListQuickActionsSharedAction.NotificationsAll -> { roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.ALL_MESSAGES)) } is RoomListQuickActionsSharedAction.NotificationsMentionsOnly -> { roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.MENTIONS_ONLY)) } - is RoomListQuickActionsSharedAction.NotificationsMute -> { + is RoomListQuickActionsSharedAction.NotificationsMute -> { roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.MUTE)) } - is RoomListQuickActionsSharedAction.Settings -> { + is RoomListQuickActionsSharedAction.Settings -> { navigator.openRoomProfile(requireActivity(), quickAction.roomId) } - is RoomListQuickActionsSharedAction.Favorite -> { + is RoomListQuickActionsSharedAction.Favorite -> { roomListViewModel.handle(RoomListAction.ToggleTag(quickAction.roomId, RoomTag.ROOM_TAG_FAVOURITE)) } - is RoomListQuickActionsSharedAction.LowPriority -> { + is RoomListQuickActionsSharedAction.LowPriority -> { roomListViewModel.handle(RoomListAction.ToggleTag(quickAction.roomId, RoomTag.ROOM_TAG_LOW_PRIORITY)) } - is RoomListQuickActionsSharedAction.Leave -> { + is RoomListQuickActionsSharedAction.Leave -> { promptLeaveRoom(quickAction.roomId) } }.exhaustive @@ -278,12 +372,7 @@ 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) + footerController.setData(state) // Mark all as read menu when (roomListParams.displayMode) { RoomListDisplayMode.NOTIFICATIONS, @@ -299,68 +388,38 @@ class RoomListFragment @Inject constructor( } } - 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.section.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 +436,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 +457,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 3a5e797f98..36551dece7 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,37 +16,50 @@ package im.vector.app.features.home.room.list +import androidx.annotation.StringRes +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope +import androidx.paging.PagedList 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.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.RoomTagQueryFilter import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.RoomCategoryFilter +import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams 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.rx +import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount +import org.matrix.android.sdk.internal.session.room.UpdatableFilterLivePageResult +import org.matrix.android.sdk.rx.asObservable 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>) + private val stringProvider: StringProvider) : VectorViewModel(initialState) { interface Factory { fun create(initialState: RoomListViewState): RoomListViewModel } + private var updatableQuery: UpdatableFilterLivePageResult? = null + companion object : MvRxViewModelFactory { @JvmStatic @@ -56,28 +69,121 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } } - private val displayMode = initialState.displayMode - private val roomListDisplayModeFilter = RoomListDisplayModeFilter(displayMode) + data class RoomsSection( + val sectionName: String, + val livePages: LiveData>, + val isExpanded: MutableLiveData = MutableLiveData(true), + val notificationCount: MutableLiveData = + MutableLiveData(RoomAggregateNotificationCount(0, 0)) + ) + + val sections: List by lazy { + val sections = mutableListOf() + if (initialState.displayMode == RoomListDisplayMode.PEOPLE) { + + addSection(sections, R.string.invitations_header) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM + } + + 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) { + 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() +// it.displayName = QueryStringValue.Contains("") + }) { qpm -> + val name = stringProvider.getString(R.string.bottom_action_rooms) + session.getFilteredPagedRoomSummariesLive(qpm) + .let { livePagedList -> + updatableQuery = livePagedList + sections.add(RoomsSection(name, livePagedList.livePagedList)) + } + } + } + sections + } init { - observeRoomSummaries() - observeMembershipChanges() } 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.SelectRoom -> handleSelectRoom(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.ToggleTag -> handleToggleTag(action) + is RoomListAction.ToggleSection -> handleToggleSection(action.section) }.exhaustive } + private fun addSection(sections: MutableList, @StringRes nameRes: Int, 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(name, livePagedList)) + } + } + } + + 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() } @@ -88,8 +194,11 @@ 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(section: RoomsSection) { + sections.find { it.sectionName == section.sectionName } + ?.let { section -> + section.isExpanded.postValue(!section.isExpanded.value.orFalse()) + } } private fun handleFilter(action: RoomListAction.FilterWith) { @@ -98,23 +207,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 { + this.memberships = Membership.activeMemberships() + this.displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) } + ) } private fun handleAcceptInvitation(action: RoomListAction.AcceptInvitation) = withState { state -> @@ -164,12 +262,12 @@ 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 { session.markAllAsRead(it, NoOpMatrixCallback()) } +// state.asyncFilteredRooms.invoke() +// ?.flatMap { it.value } +// ?.filter { it.membership == Membership.JOIN } +// ?.map { it.roomId } +// ?.toList() +// ?.let { session.markAllAsRead(it, NoOpMatrixCallback()) } } private fun handleChangeNotificationMode(action: RoomListAction.ChangeRoomNotificationState) { @@ -211,7 +309,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, private fun String.otherTag(): String? { return when (this) { - RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY + RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY RoomTag.ROOM_TAG_LOW_PRIORITY -> RoomTag.ROOM_TAG_FAVOURITE else -> null } @@ -226,46 +324,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..be4eae05db 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 @@ -17,59 +17,26 @@ 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 + val hasUnread: Boolean = false +// get() = asyncFilteredRooms.invoke() +// ?.flatMap { it.value } +// ?.filter { it.membership == Membership.JOIN } +// ?.any { it.hasUnreadMessages } +// ?: false } typealias RoomSummaries = LinkedHashMap> 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..75171dad39 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryPagedController.kt @@ -0,0 +1,71 @@ +/* + * 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.resources.StringProvider +import im.vector.app.core.resources.UserPreferencesProvider +import im.vector.app.core.utils.createUIHandler +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import javax.inject.Inject + +class RoomSummaryPagedControllerFactory @Inject constructor(private val stringProvider: StringProvider, + private val userPreferencesProvider: UserPreferencesProvider, + private val roomSummaryItemFactory: RoomSummaryItemFactory) { + + fun createRoomSummaryPagedController(): RoomSummaryPagedController { + return RoomSummaryPagedController(stringProvider, userPreferencesProvider, roomSummaryItemFactory) + } +} + +class RoomSummaryPagedController constructor(private val stringProvider: StringProvider, + private val userPreferencesProvider: UserPreferencesProvider, + private val roomSummaryItemFactory: RoomSummaryItemFactory) + : PagedListEpoxyController( +// Important it must match the PageList builder notify Looper + modelBuildingHandler = createUIHandler() +) { + + var listener: RoomListListener? = null + + override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> { + val unwrappedItem = item + // for place holder if enabled + ?: return roomSummaryItemFactory.createRoomItem( + RoomSummary( + roomId = "null_item_pos_$currentPosition", + name = "", + encryptionEventTs = null, + isEncrypted = false, + typingUsers = emptyList() + ), emptySet(), null, null) + +// GenericItem_().apply { id("null_item_pos_$currentPosition") } + + return roomSummaryItemFactory.create(unwrappedItem, emptyMap(), emptySet(), listener) + } + +// override fun onModelBound(holder: EpoxyViewHolder, boundModel: EpoxyModel<*>, position: Int, previouslyBoundModel: EpoxyModel<*>?) { +// Timber.w("VAL: Will load around $position") +// super.onModelBound(holder, boundModel, position, previouslyBoundModel) +// } +// fun onRoomLongClicked() { +// userPreferencesProvider.neverShowLongClickOnRoomHelpAgain() +// requestModelBuild() +// } +} 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..30f0e671f9 --- /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 SectionViewModel( + val name: String, + val isExpanded: Boolean = true, + val notificationCount: Int = 0, + val isHighlighted: Boolean = false, + val isHidden: Boolean = true + ) + + lateinit var section: SectionViewModel + private set + + fun updateSection(newSection: SectionViewModel) { + if (!::section.isInitialized || newSection != section) { + section = newSection + notifyDataSetChanged() + } + } + + init { + setHasStableIds(true) + } + + override fun getItemId(position: Int) = section.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(section) + } + + override fun getItemCount(): Int = 1.takeIf { section.isHidden.not() } ?: 0 + + class VH constructor( + private val binding: ItemRoomCategoryBinding, + onClickAction: (() -> Unit) + ) : RecyclerView.ViewHolder(binding.root) { + + init { + binding.root.setOnClickListener(DebouncedClickListener({ + onClickAction.invoke() + })) + } + + fun bind(section: SectionViewModel) { + binding.roomCategoryTitleView.text = section.name + val tintColor = ThemeUtils.getColor(binding.root.context, R.attr.riotx_text_secondary) + val expandedArrowDrawableRes = if (section.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(section.notificationCount, section.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) + } + } + } +} From 38af0caa3fe3f0cc4ac432ab2e393abe82c42b79 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Mar 2021 11:51:04 +0200 Subject: [PATCH 152/249] Fix / missing DM favorites --- .../vector/app/features/home/room/list/RoomListViewModel.kt | 6 ++++++ 1 file changed, 6 insertions(+) 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 36551dece7..b8dc3282b7 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 @@ -86,6 +86,12 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM } + 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 From 52f98dc40547ba79969d2481768ec1dcf0763b50 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Mar 2021 11:51:44 +0200 Subject: [PATCH 153/249] Remove HomeListDataSource --- .../android/sdk/api/query/QueryStringValue.kt | 1 - .../java/im/vector/app/AppStateHandler.kt | 50 +------------- .../im/vector/app/core/di/VectorComponent.kt | 3 - .../app/features/home/HomeDetailViewModel.kt | 67 +++++++++---------- .../features/home/HomeRoomListDataSource.kt | 25 ------- .../app/features/home/ShortcutsHandler.kt | 24 ++++--- 6 files changed, 49 insertions(+), 121 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/home/HomeRoomListDataSource.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt index 99a86e185b..8f83beface 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt @@ -33,4 +33,3 @@ sealed class QueryStringValue { INSENSITIVE } } - diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index d2b59e5513..81d1fdb636 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -19,32 +19,19 @@ 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 */ @Singleton class AppStateHandler @Inject constructor( private val sessionDataSource: ActiveSessionDataSource, - private val homeRoomListDataSource: HomeRoomListDataSource, private val selectedGroupDataSource: SelectedGroupDataSource, private val chronologicalRoomComparator: ChronologicalRoomComparator) : LifecycleObserver { @@ -52,45 +39,10 @@ class AppStateHandler @Inject constructor( @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..cae7a2ece6 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 @@ -35,7 +35,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 +112,6 @@ interface VectorComponent { fun errorFormatter(): ErrorFormatter - fun homeRoomListObservableStore(): HomeRoomListDataSource - fun selectedGroupStore(): SelectedGroupDataSource fun roomDetailPendingActionStore(): RoomDetailPendingActionStore 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..f4c66212b9 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 @@ -20,18 +20,21 @@ 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 org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.RoomCategoryFilter 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 org.matrix.android.sdk.rx.rx +import java.util.concurrent.TimeUnit /** * View model used to update the home bottom bar notification counts, observe the sync state and @@ -41,7 +44,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) { @@ -113,43 +115,40 @@ 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 ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeRoomListDataSource.kt b/vector/src/main/java/im/vector/app/features/home/HomeRoomListDataSource.kt deleted file mode 100644 index 6bcd6f01eb..0000000000 --- a/vector/src/main/java/im/vector/app/features/home/HomeRoomListDataSource.kt +++ /dev/null @@ -1,25 +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 - -import im.vector.app.core.utils.BehaviorDataSource -import org.matrix.android.sdk.api.session.room.model.RoomSummary -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class HomeRoomListDataSource @Inject constructor() : BehaviorDataSource>() 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..99cc56bd99 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,15 +21,20 @@ import android.content.pm.ShortcutManager import android.os.Build import androidx.core.content.getSystemService import androidx.core.content.pm.ShortcutManagerCompat +import im.vector.app.core.di.ActiveSessionHolder import io.reactivex.Observable 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 { @@ -38,19 +43,20 @@ class ShortcutsHandler @Inject constructor( return Observable.empty().subscribe() } - return homeRoomListStore - .observe() - .distinctUntilChanged() - .observeOn(Schedulers.computation()) - .subscribe { rooms -> + return activeSessionHolder.getSafeActiveSession()?.getPagedRoomSummariesLive(roomSummaryQueryParams { + this.memberships = listOf(Membership.JOIN) + this.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() { From 41176c3e2667f0d498259f9eb0e7a3234a2b61aa Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Mar 2021 12:31:02 +0200 Subject: [PATCH 154/249] Support only notif display mode + code quality fixes --- .../api/session/room/RoomSummaryQueryParams.kt | 1 + .../room/summary/RoomSummaryDataSource.kt | 1 + tools/check/forbidden_strings_in_code.txt | 2 +- .../app/features/home/HomeDetailViewModel.kt | 9 +++++---- .../home/room/list/RoomListViewModel.kt | 18 ++++++++++++++---- 5 files changed, 22 insertions(+), 9 deletions(-) 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 a57514023b..e0862f229a 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 @@ -27,6 +27,7 @@ fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = { enum class RoomCategoryFilter { ONLY_DM, ONLY_ROOMS, + ONLY_WITH_NOTIFICATIONS, ALL } 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 a806123237..769321cb8c 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 @@ -181,6 +181,7 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat 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 } 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/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index f4c66212b9..4810abe08b 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 @@ -115,10 +115,11 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho } private fun observeRoomSummaries() { - session.getPagedRoomSummariesLive(roomSummaryQueryParams { - memberships = Membership.activeMemberships() - }) - .asObservable() + session.getPagedRoomSummariesLive( + roomSummaryQueryParams { + memberships = Membership.activeMemberships() + } + ).asObservable() .throttleFirst(300, TimeUnit.MILLISECONDS) .subscribe { val dmInvites = session.getRoomSummaries(roomSummaryQueryParams { 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 b8dc3282b7..e02a7b8979 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 @@ -129,7 +129,6 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } else if (initialState.displayMode == RoomListDisplayMode.FILTERED) { withQueryParams({ it.memberships = Membership.activeMemberships() -// it.displayName = QueryStringValue.Contains("") }) { qpm -> val name = stringProvider.getString(R.string.bottom_action_rooms) session.getFilteredPagedRoomSummariesLive(qpm) @@ -139,10 +138,21 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } } } - sections - } + else if (initialState.displayMode == RoomListDisplayMode.NOTIFICATIONS) { + withQueryParams({ + it.memberships = Membership.activeMemberships() + it.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS + }) { qpm -> + val name = stringProvider.getString(R.string.bottom_action_rooms) + session.getFilteredPagedRoomSummariesLive(qpm) + .let { livePagedList -> + updatableQuery = livePagedList + sections.add(RoomsSection(name, livePagedList.livePagedList)) + } + } + } - init { + sections } override fun handle(action: RoomListAction) { From cf581ecfcf5d9a27534f812aebd251b317119498 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Mar 2021 13:00:20 +0200 Subject: [PATCH 155/249] Make pagedList config as part of API --- .../sdk/api/session/room/RoomService.kt | 23 ++++++++++++++++--- .../session/room/DefaultRoomService.kt | 12 ++++++---- .../room/summary/RoomSummaryDataSource.kt | 20 ++++------------ .../home/room/list/RoomListViewModel.kt | 3 +-- 4 files changed, 32 insertions(+), 26 deletions(-) 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 4814805dd8..7509c2d975 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 @@ -25,11 +25,11 @@ 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.Cancelable import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.session.room.UpdatableFilterLivePageResult import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription -import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount /** * This interface defines methods to get rooms. It's implemented at the session level. @@ -182,8 +182,25 @@ interface RoomService { */ fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback) - fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData> + fun getPagedRoomSummariesLive( + queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config = PagedList.Config.Builder() + .setPageSize(10) + .setInitialLoadSizeHint(20) + .setEnablePlaceholders(false) + .setPrefetchDistance(10) + .build() + ): LiveData> fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount - fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): UpdatableFilterLivePageResult + + fun getFilteredPagedRoomSummariesLive( + queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config = PagedList.Config.Builder() + .setPageSize(10) + .setInitialLoadSizeHint(20) + .setEnablePlaceholders(false) + .setPrefetchDistance(10) + .build() + ): UpdatableFilterLivePageResult } 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 5feb9c9d5a..34904110a1 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 @@ -30,6 +30,7 @@ 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.Cancelable import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional @@ -46,7 +47,6 @@ import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomT import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask -import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask import org.matrix.android.sdk.internal.task.TaskExecutor @@ -98,12 +98,14 @@ internal class DefaultRoomService @Inject constructor( return roomSummaryDataSource.getRoomSummariesLive(queryParams) } - override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams) : LiveData> { - return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams) + override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config) + : LiveData> { + return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams, pagedListConfig) } - override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams) : UpdatableFilterLivePageResult { - return roomSummaryDataSource.getFilteredPagedRoomSummariesLive(queryParams) + override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config) + : UpdatableFilterLivePageResult { + return roomSummaryDataSource.getFilteredPagedRoomSummariesLive(queryParams, pagedListConfig) } override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount { 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 769321cb8c..a62d82782a 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 @@ -104,7 +104,7 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat .sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX) } - fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData> { + fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config): LiveData> { val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> roomSummariesQuery(realm, queryParams) .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) @@ -113,17 +113,11 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat roomSummaryMapper.map(it) } return monarchy.findAllPagedWithChanges(realmDataSourceFactory, - LivePagedListBuilder(dataSourceFactory, - PagedList.Config.Builder() - .setPageSize(10) - .setInitialLoadSizeHint(20) - .setEnablePlaceholders(false) - .setPrefetchDistance(10) - .build()) + LivePagedListBuilder(dataSourceFactory, pagedListConfig) ) } - fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams): UpdatableFilterLivePageResult { + fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config): UpdatableFilterLivePageResult { val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> roomSummariesQuery(realm, queryParams) .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) @@ -133,13 +127,7 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat } val mapped = monarchy.findAllPagedWithChanges(realmDataSourceFactory, - LivePagedListBuilder(dataSourceFactory, - PagedList.Config.Builder() - .setPageSize(10) - .setInitialLoadSizeHint(20) - .setEnablePlaceholders(false) - .setPrefetchDistance(10) - .build()) + LivePagedListBuilder(dataSourceFactory, pagedListConfig) ) return object : UpdatableFilterLivePageResult { 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 e02a7b8979..626d058c65 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 @@ -137,8 +137,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, sections.add(RoomsSection(name, livePagedList.livePagedList)) } } - } - else if (initialState.displayMode == RoomListDisplayMode.NOTIFICATIONS) { + } else if (initialState.displayMode == RoomListDisplayMode.NOTIFICATIONS) { withQueryParams({ it.memberships = Membership.activeMemberships() it.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS From b6f0f12515b3cf64a47c6965a81e609e387f07af Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Mar 2021 13:01:25 +0200 Subject: [PATCH 156/249] lint --- .../vector/app/features/home/room/list/RoomListViewModel.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 626d058c65..4b1af3cbaf 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 @@ -209,8 +209,8 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, _viewEvents.post(RoomListViewEvents.SelectRoom(action.roomSummary)) } - private fun handleToggleSection(section: RoomsSection) { - sections.find { it.sectionName == section.sectionName } + private fun handleToggleSection(roomSection: RoomsSection) { + sections.find { it.sectionName == roomSection.sectionName } ?.let { section -> section.isExpanded.postValue(!section.isExpanded.value.orFalse()) } @@ -276,7 +276,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } } - private fun handleMarkAllRoomsRead() = withState { state -> + private fun handleMarkAllRoomsRead() = withState { _ -> // state.asyncFilteredRooms.invoke() // ?.flatMap { it.value } // ?.filter { it.membership == Membership.JOIN } From c23437d45afc9b17c5017a8b3ac3718ac2223c1c Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Mar 2021 13:57:31 +0200 Subject: [PATCH 157/249] Code quality --- .../summary/RoomAggregateNotificationCount.kt | 2 +- .../app/features/home/HomeDetailViewModel.kt | 40 +++++++++++-------- 2 files changed, 25 insertions(+), 17 deletions(-) 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 index 2aa122c84e..0c581508b8 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * 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. 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 4810abe08b..07e2426200 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 @@ -122,25 +122,33 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho ).asObservable() .throttleFirst(300, TimeUnit.MILLISECONDS) .subscribe { - val dmInvites = session.getRoomSummaries(roomSummaryQueryParams { - memberships = listOf(Membership.INVITE) - roomCategoryFilter = RoomCategoryFilter.ONLY_DM - }).size + val dmInvites = session.getRoomSummaries( + roomSummaryQueryParams { + memberships = listOf(Membership.INVITE) + roomCategoryFilter = RoomCategoryFilter.ONLY_DM + } + ).size - val roomsInvite = session.getRoomSummaries(roomSummaryQueryParams { - memberships = listOf(Membership.INVITE) - roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS - }).size + val roomsInvite = session.getRoomSummaries( + roomSummaryQueryParams { + memberships = listOf(Membership.INVITE) + roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + } + ).size - val dmRooms = session.getNotificationCountForRooms(roomSummaryQueryParams { - memberships = listOf(Membership.JOIN) - roomCategoryFilter = RoomCategoryFilter.ONLY_DM - }) + val dmRooms = session.getNotificationCountForRooms( + roomSummaryQueryParams { + memberships = listOf(Membership.JOIN) + roomCategoryFilter = RoomCategoryFilter.ONLY_DM + } + ) - val otherRooms = session.getNotificationCountForRooms(roomSummaryQueryParams { - memberships = listOf(Membership.JOIN) - roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS - }) + val otherRooms = session.getNotificationCountForRooms( + roomSummaryQueryParams { + memberships = listOf(Membership.JOIN) + roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + } + ) setState { copy( From b390980ca2cb70d221529faa0b8202642d9bef4f Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Mar 2021 16:51:32 +0200 Subject: [PATCH 158/249] Resurrect mark all as read --- .../app/features/home/HomeDetailAction.kt | 1 + .../app/features/home/HomeDetailFragment.kt | 33 +++++++++++++++-- .../app/features/home/HomeDetailViewModel.kt | 30 +++++++++++++++- .../app/features/home/HomeDetailViewState.kt | 1 + .../features/home/room/list/RoomListAction.kt | 1 - .../home/room/list/RoomListFragment.kt | 35 ------------------- .../home/room/list/RoomListViewModel.kt | 10 ------ 7 files changed, 62 insertions(+), 49 deletions(-) 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 07e2426200..8a73b2e0f9 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,6 +16,7 @@ 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 @@ -28,12 +29,16 @@ 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 kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.RoomCategoryFilter 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 /** @@ -77,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() } } @@ -92,6 +98,27 @@ 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 { + this.memberships = listOf(Membership.JOIN) + this.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() @@ -157,7 +184,8 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho notificationCountPeople = dmRooms.totalCount() + dmInvites, notificationHighlightPeople = dmRooms.isHighlight() || dmInvites > 0, notificationCountRooms = otherRooms.totalCount() + roomsInvite, - notificationHighlightRooms = otherRooms.isHighlight() || roomsInvite > 0 + 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/room/list/RoomListAction.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListAction.kt index 0d70f99b3a..a5c071680d 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 @@ -29,5 +29,4 @@ sealed class RoomListAction : VectorViewModelAction { 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/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index 0e7e35654c..ac59283e7a 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,8 +20,6 @@ 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 @@ -84,8 +82,6 @@ class RoomListFragment @Inject constructor( return FragmentRoomListBinding.inflate(inflater, container, false) } - private var hasUnreadRooms = false - data class SectionKey( val name: String, val isExpanded: Boolean @@ -100,24 +96,6 @@ class RoomListFragment @Inject constructor( private val adapterInfosList = mutableListOf() private var concatAdapter: ConcatAdapter? = null - override fun getMenuRes() = R.menu.room_list - - 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) - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupCreateRoomButton() @@ -373,19 +351,6 @@ class RoomListFragment @Inject constructor( override fun invalidate() = withState(roomListViewModel) { state -> footerController.setData(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 - } } private fun checkEmptyState() { 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 4b1af3cbaf..76e8b1adaf 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 @@ -160,7 +160,6 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, 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) @@ -276,15 +275,6 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } } - private fun handleMarkAllRoomsRead() = withState { _ -> -// state.asyncFilteredRooms.invoke() -// ?.flatMap { it.value } -// ?.filter { it.membership == Membership.JOIN } -// ?.map { it.roomId } -// ?.toList() -// ?.let { session.markAllAsRead(it, NoOpMatrixCallback()) } - } - private fun handleChangeNotificationMode(action: RoomListAction.ChangeRoomNotificationState) { val room = session.getRoom(action.roomId) if (room != null) { From cd6fab0e2d35a0a5d92f563acf35188bea84aaa8 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 31 Mar 2021 17:36:24 +0200 Subject: [PATCH 159/249] Fix empty state + cleaning + changelog --- CHANGES.md | 1 + .../im/vector/app/features/home/room/list/RoomListFragment.kt | 2 +- .../vector/app/features/home/room/list/SectionHeaderAdapter.kt | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ed549c918f..45b3905375 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,7 @@ 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) + - Room list improvements (paging) Bugfix 🐛: - Fix bad theme change for the MainActivity 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 ac59283e7a..cf74cae37c 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 @@ -249,7 +249,7 @@ class RoomListFragment @Inject constructor( val contentAdapter = pagedControllerFactory.createRoomSummaryPagedController().also { section.livePages.observe(viewLifecycleOwner) { pl -> it.submitList(pl) - sectionAdapter.updateSection(sectionAdapter.section.copy(isHidden = pl.isEmpty() || hasOnlyOneSection)) + sectionAdapter.updateSection(sectionAdapter.section.copy(isHidden = pl.isEmpty())) checkEmptyState() } section.notificationCount.observe(viewLifecycleOwner) { counts -> 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 index 30f0e671f9..b137947ef7 100644 --- 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 @@ -64,7 +64,7 @@ class SectionHeaderAdapter constructor( holder.bind(section) } - override fun getItemCount(): Int = 1.takeIf { section.isHidden.not() } ?: 0 + override fun getItemCount(): Int = if (section.isHidden) 0 else 1 class VH constructor( private val binding: ItemRoomCategoryBinding, From f998c2f945b40d6d373778da61d566bf5b2b3ef8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 31 Mar 2021 22:53:29 +0200 Subject: [PATCH 160/249] Fix avatar rendering for DMs, after initial sync (#2693) Also better handling of previous DMs management --- CHANGES.md | 1 + .../query/RoomSummaryEntityQueries.kt | 8 +++++- .../session/room/RoomAvatarResolver.kt | 8 +++--- .../sync/UserAccountDataSyncHandler.kt | 27 ++++++++++++------- .../accountdata/DirectMessagesContent.kt | 5 +++- 5 files changed, 33 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ed549c918f..1601e0ec85 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,6 +19,7 @@ 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 🗣: - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt index 7430b7822f..2af5dcf0ae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt @@ -48,9 +48,15 @@ internal fun RoomSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: Strin return where(realm, roomId).findFirst() ?: realm.createObject(roomId) } -internal fun RoomSummaryEntity.Companion.getDirectRooms(realm: Realm): RealmResults { +internal fun RoomSummaryEntity.Companion.getDirectRooms(realm: Realm, + excludeRoomIds: Set? = null): RealmResults { return RoomSummaryEntity.where(realm) .equalTo(RoomSummaryEntityFields.IS_DIRECT, true) + .apply { + if (!excludeRoomIds.isNullOrEmpty()) { + not().`in`(RoomSummaryEntityFields.ROOM_ID, excludeRoomIds.toTypedArray()) + } + } .findAll() } 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 99f9d3644d..9bcb1eb196 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 @@ -16,18 +16,18 @@ package org.matrix.android.sdk.internal.session.room +import io.realm.Realm 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.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.query.getOrNull +import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper -import io.realm.Realm -import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity -import org.matrix.android.sdk.internal.database.query.where import javax.inject.Inject internal class RoomAvatarResolver @Inject constructor(@UserId private val userId: String) { @@ -48,7 +48,7 @@ internal class RoomAvatarResolver @Inject constructor(@UserId private val userId 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 ?: false if (isDirectRoom) { if (members.size == 1) { res = members.firstOrNull()?.avatarUrl 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 449d47abe5..907c1187fe 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 @@ -45,6 +45,7 @@ import org.matrix.android.sdk.internal.database.query.getOrCreate 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.RoomMemberHelper import org.matrix.android.sdk.internal.session.sync.model.InvitedRoomSync import org.matrix.android.sdk.internal.session.sync.model.accountdata.BreadcrumbsContent @@ -60,7 +61,9 @@ internal class UserAccountDataSyncHandler @Inject constructor( @SessionDatabase private val monarchy: Monarchy, @UserId private val userId: String, private val directChatsHelper: DirectChatsHelper, - private val updateUserAccountDataTask: UpdateUserAccountDataTask) { + private val updateUserAccountDataTask: UpdateUserAccountDataTask, + private val roomAvatarResolver: RoomAvatarResolver +) { fun handle(realm: Realm, accountData: UserAccountDataSync?) { accountData?.list?.forEach { event -> @@ -151,23 +154,27 @@ internal class UserAccountDataSyncHandler @Inject constructor( } private fun handleDirectChatRooms(realm: Realm, event: UserAccountDataEvent) { - val oldDirectRooms = RoomSummaryEntity.getDirectRooms(realm) - oldDirectRooms.forEach { - it.isDirect = false - it.directUserId = null - } val content = event.content.toModel() ?: return - content.forEach { - val userId = it.key - it.value.forEach { roomId -> + content.forEach { (userId, roomIds) -> + roomIds.forEach { roomId -> val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() if (roomSummaryEntity != null) { roomSummaryEntity.isDirect = true roomSummaryEntity.directUserId = userId - realm.insertOrUpdate(roomSummaryEntity) + // Also update the avatar, there is a specific treatment for DMs + roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId) } } } + + // Handle previous direct rooms + RoomSummaryEntity.getDirectRooms(realm, excludeRoomIds = content.values.flatten().toSet()) + .forEach { + it.isDirect = false + it.directUserId = null + // Also update the avatar, there was a specific treatment for DMs + it.avatarUrl = roomAvatarResolver.resolve(realm, it.roomId) + } } private fun handleIgnoredUsers(realm: Realm, event: UserAccountDataEvent) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt index c406f3acf1..41173dea96 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt @@ -16,4 +16,7 @@ package org.matrix.android.sdk.internal.session.sync.model.accountdata -typealias DirectMessagesContent = Map> +/** + * Keys are userIds, values are list of roomIds + */ +internal typealias DirectMessagesContent = Map> From fe80b7bd6a3b6d899e4fbf22b4ef42e88a7b2cd0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 31 Mar 2021 23:16:32 +0200 Subject: [PATCH 161/249] Dominaezzz' review: remove withContext usage --- .../session/terms/DefaultTermsService.kt | 58 +++++++++---------- 1 file changed, 26 insertions(+), 32 deletions(-) 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 73545205d1..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,24 +17,22 @@ 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 import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate import org.matrix.android.sdk.internal.network.NetworkConstants import org.matrix.android.sdk.internal.network.RetrofitFactory +import org.matrix.android.sdk.internal.network.executeRequest 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 org.matrix.android.sdk.internal.network.executeRequest 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.io) { - val url = buildUrl(baseUrl, serviceType) - val termsResponse = executeRequest(null) { - 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.io) { - 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) { - 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" } From 52ba67c9c04c9de52d2b9f98b34ba878d20c588b Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 1 Apr 2021 09:12:18 +0200 Subject: [PATCH 162/249] unused val --- .../im/vector/app/features/home/room/list/RoomListFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 cf74cae37c..05bf9a8a84 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 @@ -235,7 +235,7 @@ class RoomListFragment @Inject constructor( modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) } val concatAdapter = ConcatAdapter() - val hasOnlyOneSection = roomListViewModel.sections.size == 1 +// val hasOnlyOneSection = roomListViewModel.sections.size == 1 roomListViewModel.sections.forEach { section -> val sectionAdapter = SectionHeaderAdapter { From ba27a601dd6a03ec468b0a629247e2d7ab9a41b2 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 1 Apr 2021 11:44:53 +0200 Subject: [PATCH 163/249] Dominaezzz' review: remove Request class, just use executeRequest() --- .../internal/crypto/tasks/SendToDeviceTask.kt | 21 +++--- .../crypto/tasks/UploadSignaturesTask.kt | 11 ++-- .../android/sdk/internal/network/Request.kt | 65 ++++++++----------- .../room/membership/joining/InviteTask.kt | 14 ++-- .../session/room/read/SetReadMarkersTask.kt | 10 +-- .../session/room/timeline/PaginationTask.kt | 10 +-- 6 files changed, 61 insertions(+), 70 deletions(-) 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 fe6868968b..b3fb704131 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 @@ -48,17 +48,14 @@ internal class DefaultSendToDeviceTask @Inject constructor( return executeRequest( globalErrorReceiver, - { - cryptoApi.sendToDevice( - params.eventType, - params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(), - sendToDeviceBody - ) - }, - { - isRetryable = true - maxRetryCount = 3 - } - ) + isRetryable = true, + maxRetryCount = 3 + ) { + cryptoApi.sendToDevice( + params.eventType, + params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(), + sendToDeviceBody + ) + } } } 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 bac886cafc..15cf017dc9 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 @@ -37,12 +37,11 @@ internal class DefaultUploadSignaturesTask @Inject constructor( try { val response = executeRequest( globalErrorReceiver, - { cryptoApi.uploadSignatures(params.signatures) }, - { - isRetryable = true - maxRetryCount = 10 - } - ) + isRetryable = true, + maxRetryCount = 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/network/Request.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt index 9f3000ce18..9aa242aecf 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 @@ -25,44 +25,35 @@ import retrofit2.HttpException import timber.log.Timber import java.io.IOException -// To use when there is no init block to provide -internal suspend inline fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, - noinline requestBlock: suspend () -> DATA): DATA { - return executeRequest(globalErrorReceiver, requestBlock, {}) -} +/** + * Execute a request from the requestBlock and handle some of the Exception it could generate + * + * @param globalErrorReceiver will be use to notify error such as invalid token error. See [GlobalError] + * @param isRetryable if set to true, the request will be executed again in case of error, after a delay + * @param initialDelay the first delay to wait before a request is retried. Will be doubled after each retry + * @param maxDelay the max delay to wait before a retry + * @param maxRetryCount the max number of retries + * @param requestBlock a suspend lambda to perform the network request + */ +internal suspend inline fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, + isRetryable: Boolean = false, + initialDelay: Long = 100L, + maxDelay: Long = 10_000L, + maxRetryCount: Int = Int.MAX_VALUE, + noinline requestBlock: suspend () -> DATA): DATA { + var currentRetryCount = 0 + var currentDelay = initialDelay -internal suspend inline fun executeRequest(globalErrorReceiver: GlobalErrorReceiver?, - noinline requestBlock: suspend () -> DATA, - initBlock: Request.() -> Unit): DATA { - return Request(globalErrorReceiver, requestBlock) - .apply(initBlock) - .execute() -} - -internal class Request( - private val globalErrorReceiver: GlobalErrorReceiver?, - private val requestBlock: suspend () -> DATA -) { - - 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 - - suspend fun execute(): DATA { - return try { - try { - requestBlock() - } catch (exception: Throwable) { - throw when (exception) { - is KotlinNullPointerException -> IllegalStateException("The request returned a null body") - is HttpException -> exception.toFailure(globalErrorReceiver) - else -> exception - } + 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 } - } catch (exception: Throwable) { + // Log some details about the request which has failed. This is less useful than before... // Timber.e("Exception when executing request ${apiCall.request().method} ${apiCall.request().url.toString().substringBefore("?")}") Timber.e("Exception when executing request") @@ -79,7 +70,7 @@ internal class Request( if (isRetryable && currentRetryCount++ < maxRetryCount && exception.shouldBeRetried()) { delay(currentDelay) currentDelay = (currentDelay * 2L).coerceAtMost(maxDelay) - return execute() + // 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/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 0f141c344a..ab1299c34a 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 @@ -37,12 +37,12 @@ internal class DefaultInviteTask @Inject constructor( override suspend fun execute(params: InviteTask.Params) { val body = InviteBody(params.userId, params.reason) - return executeRequest(globalErrorReceiver, - { roomAPI.invite(params.roomId, body) }, - { - isRetryable = true - maxRetryCount = 3 - } - ) + return executeRequest( + globalErrorReceiver, + isRetryable = true, + maxRetryCount = 3 + ) { + roomAPI.invite(params.roomId, body) + } } } 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 23a3f2d36c..aa1eaff119 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 @@ -96,10 +96,12 @@ internal class DefaultSetReadMarkersTask @Inject constructor( updateDatabase(params.roomId, markers, shouldUpdateRoomSummary) } if (markers.isNotEmpty()) { - executeRequest(globalErrorReceiver, - { roomAPI.sendReadMarker(params.roomId, markers) }, - { isRetryable = true } - ) + executeRequest( + globalErrorReceiver, + isRetryable = true + ) { + roomAPI.sendReadMarker(params.roomId, markers) + } } } 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 b1ce5c1f26..ac46c9c9ff 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,10 +42,12 @@ internal class DefaultPaginationTask @Inject constructor( override suspend fun execute(params: PaginationTask.Params): TokenChunkEventPersistor.Result { val filter = filterRepository.getRoomFilter() - val chunk = executeRequest(globalErrorReceiver, - { roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter) }, - { isRetryable = true } - ) + val chunk = executeRequest( + globalErrorReceiver, + isRetryable = true + ) { + roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter) + } return tokenChunkEventPersistor.insertInDb(chunk, params.roomId, params.direction) } } From 1f2d6bea21b79512ca5414c291e3ba3acf25d409 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 1 Apr 2021 12:11:49 +0200 Subject: [PATCH 164/249] Do some renaming --- .../internal/crypto/tasks/SendToDeviceTask.kt | 4 ++-- .../crypto/tasks/UploadSignaturesTask.kt | 4 ++-- .../android/sdk/internal/network/Request.kt | 22 +++++++++---------- .../room/membership/joining/InviteTask.kt | 4 ++-- .../session/room/read/SetReadMarkersTask.kt | 2 +- .../session/room/timeline/PaginationTask.kt | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) 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 b3fb704131..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 @@ -48,8 +48,8 @@ internal class DefaultSendToDeviceTask @Inject constructor( return executeRequest( globalErrorReceiver, - isRetryable = true, - maxRetryCount = 3 + canRetry = true, + maxRetriesCount = 3 ) { cryptoApi.sendToDevice( params.eventType, 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 15cf017dc9..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 @@ -37,8 +37,8 @@ internal class DefaultUploadSignaturesTask @Inject constructor( try { val response = executeRequest( globalErrorReceiver, - isRetryable = true, - maxRetryCount = 10 + canRetry = true, + maxRetriesCount = 10 ) { cryptoApi.uploadSignatures(params.signatures) } 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 9aa242aecf..24b9b83f75 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 @@ -29,20 +29,20 @@ import java.io.IOException * Execute a request from the requestBlock and handle some of the Exception it could generate * * @param globalErrorReceiver will be use to notify error such as invalid token error. See [GlobalError] - * @param isRetryable if set to true, the request will be executed again in case of error, after a delay - * @param initialDelay the first delay to wait before a request is retried. Will be doubled after each retry - * @param maxDelay the max delay to wait before a retry - * @param maxRetryCount the max number of retries + * @param canRetry if set to true, the request will be executed again in case of error, after a delay + * @param initialDelayBeforeRetry the first delay to wait before a request is retried. Will be doubled after each retry + * @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?, - isRetryable: Boolean = false, - initialDelay: Long = 100L, - maxDelay: Long = 10_000L, - maxRetryCount: Int = Int.MAX_VALUE, + canRetry: Boolean = false, + initialDelayBeforeRetry: Long = 100L, + maxDelayBeforeRetry: Long = 10_000L, + maxRetriesCount: Int = Int.MAX_VALUE, noinline requestBlock: suspend () -> DATA): DATA { var currentRetryCount = 0 - var currentDelay = initialDelay + var currentDelay = initialDelayBeforeRetry while (true) { try { @@ -67,9 +67,9 @@ internal suspend inline fun executeRequest(globalErrorReceiver: GlobalErr // } ?.also { unrecognizedCertificateException -> throw unrecognizedCertificateException } - if (isRetryable && currentRetryCount++ < maxRetryCount && exception.shouldBeRetried()) { + if (canRetry && currentRetryCount++ < maxRetriesCount && exception.shouldBeRetried()) { delay(currentDelay) - currentDelay = (currentDelay * 2L).coerceAtMost(maxDelay) + currentDelay = (currentDelay * 2L).coerceAtMost(maxDelayBeforeRetry) // Try again (loop) } else { throw when (exception) { 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 ab1299c34a..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 @@ -39,8 +39,8 @@ internal class DefaultInviteTask @Inject constructor( val body = InviteBody(params.userId, params.reason) return executeRequest( globalErrorReceiver, - isRetryable = true, - maxRetryCount = 3 + 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/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt index aa1eaff119..35dc6a4f0c 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 @@ -98,7 +98,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor( if (markers.isNotEmpty()) { executeRequest( globalErrorReceiver, - isRetryable = true + canRetry = true ) { roomAPI.sendReadMarker(params.roomId, markers) } 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 ac46c9c9ff..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 @@ -44,7 +44,7 @@ internal class DefaultPaginationTask @Inject constructor( val filter = filterRepository.getRoomFilter() val chunk = executeRequest( globalErrorReceiver, - isRetryable = true + canRetry = true ) { roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter) } From 0bc864fc37cc6b5ec5cd9f31bcedec1f4b1938a5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 1 Apr 2021 13:51:16 +0200 Subject: [PATCH 165/249] Better handling on 429 --- .../org/matrix/android/sdk/api/failure/Extensions.kt | 12 ++++++++++++ .../matrix/android/sdk/internal/network/Request.kt | 6 ++++-- .../room/send/queue/EventSenderProcessorCoroutine.kt | 4 ++-- 3 files changed, 18 insertions(+), 4 deletions(-) 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..17362ff8d7 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 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 24b9b83f75..d9f102c7e0 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,6 +19,7 @@ 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.getRetryDelay import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.internal.network.ssl.CertUtil import retrofit2.HttpException @@ -68,8 +69,9 @@ internal suspend inline fun executeRequest(globalErrorReceiver: GlobalErr ?.also { unrecognizedCertificateException -> throw unrecognizedCertificateException } if (canRetry && currentRetryCount++ < maxRetriesCount && exception.shouldBeRetried()) { - delay(currentDelay) - currentDelay = (currentDelay * 2L).coerceAtMost(maxDelayBeforeRetry) + // In case of 429, ensure we wait enough + delay(currentDelay.coerceAtLeast(exception.getRetryDelay(0))) + currentDelay = currentDelay.times(2L).coerceAtMost(maxDelayBeforeRetry) // Try again (loop) } else { throw when (exception) { 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") From ec3266f7e8cfe30e339db16888d4ac89f15e71a3 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 1 Apr 2021 15:47:58 +0200 Subject: [PATCH 166/249] remove dead code --- .../home/room/list/RoomListViewState.kt | 25 ------------------- 1 file changed, 25 deletions(-) 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 be4eae05db..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,12 +16,9 @@ package im.vector.app.features.home.room.list -import androidx.annotation.StringRes import com.airbnb.mvrx.MvRxState -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.RoomSummary data class RoomListViewState( val displayMode: RoomListDisplayMode, @@ -30,26 +27,4 @@ data class RoomListViewState( ) : MvRxState { constructor(args: RoomListParams) : this(displayMode = args.displayMode) - - val hasUnread: Boolean = false -// 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() } From bf6058dc32b24ca6b36eec90883dc02fe5753a97 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 1 Apr 2021 15:55:42 +0200 Subject: [PATCH 167/249] Show local echo of joining action --- .../home/room/list/RoomListFragment.kt | 14 +++- .../home/room/list/RoomListViewModel.kt | 65 +++++++++++++++---- .../room/list/RoomSummaryPagedController.kt | 24 +++---- 3 files changed, 76 insertions(+), 27 deletions(-) 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 05bf9a8a84..8323c3b155 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 @@ -84,7 +84,8 @@ class RoomListFragment @Inject constructor( data class SectionKey( val name: String, - val isExpanded: Boolean + val isExpanded: Boolean, + val notifyOfLocalEcho: Boolean ) data class SectionAdapterInfo( @@ -116,6 +117,14 @@ 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() { @@ -267,7 +276,8 @@ class RoomListFragment @Inject constructor( SectionAdapterInfo( SectionKey( name = section.sectionName, - isExpanded = section.isExpanded.value.orTrue() + isExpanded = section.isExpanded.value.orTrue(), + notifyOfLocalEcho = section.notifyOfLocalEcho ), sectionAdapter, contentAdapter 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 76e8b1adaf..21e4e5830c 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 @@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.query.RoomTagQueryFilter import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.RoomCategoryFilter import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams +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 @@ -46,6 +47,7 @@ import org.matrix.android.sdk.api.session.room.state.isPublic import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.internal.session.room.UpdatableFilterLivePageResult import org.matrix.android.sdk.rx.asObservable +import org.matrix.android.sdk.rx.rx import timber.log.Timber import javax.inject.Inject @@ -60,6 +62,19 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, private var updatableQuery: UpdatableFilterLivePageResult? = null + init { + observeMembershipChanges() + } + + private fun observeMembershipChanges() { + session.rx() + .liveRoomChangeMembershipState() + .subscribe { + setState { copy(roomMembershipChanges = it) } + } + .disposeOnClear() + } + companion object : MvRxViewModelFactory { @JvmStatic @@ -74,14 +89,15 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, val livePages: LiveData>, val isExpanded: MutableLiveData = MutableLiveData(true), val notificationCount: MutableLiveData = - MutableLiveData(RoomAggregateNotificationCount(0, 0)) + MutableLiveData(RoomAggregateNotificationCount(0, 0)), + val notifyOfLocalEcho: Boolean = false ) val sections: List by lazy { val sections = mutableListOf() if (initialState.displayMode == RoomListDisplayMode.PEOPLE) { - addSection(sections, R.string.invitations_header) { + addSection(sections, R.string.invitations_header, true) { it.memberships = listOf(Membership.INVITE) it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM } @@ -98,7 +114,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } } else if (initialState.displayMode == RoomListDisplayMode.ROOMS) { - addSection(sections, R.string.invitations_header) { + addSection(sections, R.string.invitations_header, true) { it.memberships = listOf(Membership.INVITE) it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS } @@ -138,16 +154,15 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } } } else if (initialState.displayMode == RoomListDisplayMode.NOTIFICATIONS) { - withQueryParams({ - it.memberships = Membership.activeMemberships() + + 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 - }) { qpm -> - val name = stringProvider.getString(R.string.bottom_action_rooms) - session.getFilteredPagedRoomSummariesLive(qpm) - .let { livePagedList -> - updatableQuery = livePagedList - sections.add(RoomsSection(name, livePagedList.livePagedList)) - } } } @@ -167,7 +182,10 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, }.exhaustive } - private fun addSection(sections: MutableList, @StringRes nameRes: Int, query: (RoomSummaryQueryParams.Builder) -> Unit) { + private fun addSection(sections: MutableList, + @StringRes nameRes: Int, + notifyOfLocalEcho: Boolean = false, + query: (RoomSummaryQueryParams.Builder) -> Unit) { withQueryParams({ query.invoke(it) }) { roomQueryParams -> @@ -185,7 +203,13 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, ?.postValue(session.getNotificationCountForRooms(roomQueryParams)) }.disposeOnClear() - sections.add(RoomsSection(name, livePagedList)) + sections.add( + RoomsSection( + sectionName = name, + livePages = livePagedList, + notifyOfLocalEcho = notifyOfLocalEcho + ) + ) } } } @@ -238,6 +262,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 { 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 index 75171dad39..38137e0033 100644 --- 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 @@ -18,24 +18,19 @@ package im.vector.app.features.home.room.list import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.paging.PagedListEpoxyController -import im.vector.app.core.resources.StringProvider -import im.vector.app.core.resources.UserPreferencesProvider 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 stringProvider: StringProvider, - private val userPreferencesProvider: UserPreferencesProvider, - private val roomSummaryItemFactory: RoomSummaryItemFactory) { +class RoomSummaryPagedControllerFactory @Inject constructor(private val roomSummaryItemFactory: RoomSummaryItemFactory) { fun createRoomSummaryPagedController(): RoomSummaryPagedController { - return RoomSummaryPagedController(stringProvider, userPreferencesProvider, roomSummaryItemFactory) + return RoomSummaryPagedController(roomSummaryItemFactory) } } -class RoomSummaryPagedController constructor(private val stringProvider: StringProvider, - private val userPreferencesProvider: UserPreferencesProvider, - private val roomSummaryItemFactory: RoomSummaryItemFactory) +class RoomSummaryPagedController constructor(private val roomSummaryItemFactory: RoomSummaryItemFactory) : PagedListEpoxyController( // Important it must match the PageList builder notify Looper modelBuildingHandler = createUIHandler() @@ -43,9 +38,16 @@ class RoomSummaryPagedController constructor(private val stringProvider: StringP 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<*> { val unwrappedItem = item - // for place holder if enabled + // for place holder if enabled ?: return roomSummaryItemFactory.createRoomItem( RoomSummary( roomId = "null_item_pos_$currentPosition", @@ -57,7 +59,7 @@ class RoomSummaryPagedController constructor(private val stringProvider: StringP // GenericItem_().apply { id("null_item_pos_$currentPosition") } - return roomSummaryItemFactory.create(unwrappedItem, emptyMap(), emptySet(), listener) + return roomSummaryItemFactory.create(unwrappedItem, roomChangeMembershipStates ?: emptyMap(), emptySet(), listener) } // override fun onModelBound(holder: EpoxyViewHolder, boundModel: EpoxyModel<*>, position: Int, previouslyBoundModel: EpoxyModel<*>?) { From f60e649d76b2402a7d7ca0079165744ed50fa49a Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 1 Apr 2021 15:56:03 +0200 Subject: [PATCH 168/249] Fix / newly joined or created are at the bottom of room list --- .../session/room/create/CreateRoomTask.kt | 15 ++++++++---- .../room/membership/joining/JoinRoomTask.kt | 23 +++++++++++++------ 2 files changed, 27 insertions(+), 11 deletions(-) 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..cf5e4d64cb 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 @@ -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/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt index 3b7639d42f..efb20863b5 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,24 @@ 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 @@ -69,12 +72,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) } From b47ced68b5296bd90fc656c94d1fafb808137583 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 1 Apr 2021 14:39:02 +0200 Subject: [PATCH 169/249] Move UpdatableFilterLivePageResult to the correct package --- .../sdk/api/session/room/RoomService.kt | 1 - .../room/UpdatableFilterLivePageResult.kt | 27 +++++++++++++++++++ .../session/room/DefaultRoomService.kt | 5 +--- .../room/summary/RoomSummaryDataSource.kt | 2 +- .../home/room/list/RoomListViewModel.kt | 2 +- 5 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt 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 7509c2d975..819ba054ea 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 @@ -28,7 +28,6 @@ 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.Cancelable import org.matrix.android.sdk.api.util.Optional -import org.matrix.android.sdk.internal.session.room.UpdatableFilterLivePageResult import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt new file mode 100644 index 0000000000..38462d5ac6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.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 org.matrix.android.sdk.api.session.room + +import androidx.lifecycle.LiveData +import androidx.paging.PagedList +import org.matrix.android.sdk.api.session.room.model.RoomSummary + +interface UpdatableFilterLivePageResult { + val livePagedList: LiveData> + + fun updateQuery(queryParams: RoomSummaryQueryParams) +} 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 34904110a1..f1d4bfca3f 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 @@ -25,6 +25,7 @@ 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 @@ -195,7 +196,3 @@ internal class DefaultRoomService @Inject constructor( } } -interface UpdatableFilterLivePageResult { - val livePagedList: LiveData> - fun updateQuery(queryParams: RoomSummaryQueryParams) -} 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 a62d82782a..02ecce6afa 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 @@ -26,6 +26,7 @@ import io.realm.RealmQuery import io.realm.Sort import org.matrix.android.sdk.api.session.room.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 @@ -38,7 +39,6 @@ import org.matrix.android.sdk.internal.database.query.findByAlias 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.session.room.UpdatableFilterLivePageResult import org.matrix.android.sdk.internal.util.fetchCopyMap import javax.inject.Inject 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 21e4e5830c..2a214e6e90 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 @@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.query.RoomTagQueryFilter import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.room.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.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -45,7 +46,6 @@ 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.api.session.room.summary.RoomAggregateNotificationCount -import org.matrix.android.sdk.internal.session.room.UpdatableFilterLivePageResult import org.matrix.android.sdk.rx.asObservable import org.matrix.android.sdk.rx.rx import timber.log.Timber From b9f73c6cc3056706fda07a586a8ce39c1c129d4c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 1 Apr 2021 18:19:05 +0200 Subject: [PATCH 170/249] BMA's cleanup --- .../sdk/api/query/RoomCategoryFilter.kt | 24 ++++ .../sdk/api/session/room/RoomService.kt | 39 +++--- .../session/room/RoomSummaryQueryParams.kt | 8 +- .../summary/RoomAggregateNotificationCount.kt | 5 +- .../SessionRealmConfigurationFactory.kt | 1 - .../database/model/RoomSummaryEntity.kt | 19 ++- .../room/summary/RoomSummaryDataSource.kt | 27 ++-- .../room/summary/RoomSummaryUpdater.kt | 3 - .../java/im/vector/app/AppStateHandler.kt | 7 +- .../app/core/platform/VectorBaseFragment.kt | 2 + .../app/features/home/HomeDetailViewModel.kt | 30 ++-- .../app/features/home/ShortcutsHandler.kt | 15 +- .../features/home/room/list/RoomListAction.kt | 2 +- .../home/room/list/RoomListFragment.kt | 98 ++++++------- .../home/room/list/RoomListViewModel.kt | 129 ++++++++---------- .../room/list/RoomSummaryPagedController.kt | 45 +++--- .../features/home/room/list/RoomsSection.kt | 31 +++++ .../home/room/list/SectionHeaderAdapter.kt | 24 ++-- 18 files changed, 266 insertions(+), 243 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomCategoryFilter.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/RoomsSection.kt 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/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index 819ba054ea..8c833644ee 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 @@ -181,25 +181,28 @@ interface RoomService { */ fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback) - fun getPagedRoomSummariesLive( - queryParams: RoomSummaryQueryParams, - pagedListConfig: PagedList.Config = PagedList.Config.Builder() - .setPageSize(10) - .setInitialLoadSizeHint(20) - .setEnablePlaceholders(false) - .setPrefetchDistance(10) - .build() - ): LiveData> + /** + * 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 - fun getFilteredPagedRoomSummariesLive( - queryParams: RoomSummaryQueryParams, - pagedListConfig: PagedList.Config = PagedList.Config.Builder() - .setPageSize(10) - .setInitialLoadSizeHint(20) - .setEnablePlaceholders(false) - .setPrefetchDistance(10) - .build() - ): UpdatableFilterLivePageResult + 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 e0862f229a..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,7 @@ 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 @@ -24,13 +25,6 @@ fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = { return RoomSummaryQueryParams.Builder().apply(init).build() } -enum class RoomCategoryFilter { - ONLY_DM, - ONLY_ROOMS, - ONLY_WITH_NOTIFICATIONS, - ALL -} - /** * This class can be used to filter room summaries to use with: * [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService] 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 index 0c581508b8..066178b1ec 100644 --- 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 @@ -20,7 +20,6 @@ data class RoomAggregateNotificationCount( val notificationCount: Int, val highlightCount: Int ) { - fun totalCount() = notificationCount + highlightCount - - fun isHighlight() = highlightCount > 0 + val totalCount = notificationCount + highlightCount + val isHighlight = highlightCount > 0 } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt index 770ef46080..244fe3432a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt @@ -72,7 +72,6 @@ internal class SessionRealmConfigurationFactory @Inject constructor( .allowWritesOnUiThread(true) .modules(SessionRealmModule()) .schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION) -// .deleteRealmIfMigrationNeeded() .migration(migration) .build() 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 6007ae504a..669074ffd2 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 @@ -106,7 +106,7 @@ internal open class RoomSummaryEntity( private var tags: RealmList = RealmList() - fun tags(): RealmList = tags + fun tags(): List = tags fun updateTags(newTags: List>) { val toDelete = mutableListOf() @@ -118,9 +118,9 @@ internal open class RoomSummaryEntity( existingTag.tagOrder = updatedTag.second } } - toDelete.onEach { it.deleteFromRealm() } + toDelete.forEach { it.deleteFromRealm() } newTags.forEach { newTag -> - if (tags.indexOfFirst { it.tagName == newTag.first } == -1) { + if (tags.all { it.tagName != newTag.first }) { // we must add it tags.add( RoomTagEntity(newTag.first, newTag.second) @@ -128,9 +128,9 @@ internal open class RoomSummaryEntity( } } - isFavourite = newTags.indexOfFirst { it.first == RoomTag.ROOM_TAG_FAVOURITE } != -1 - isLowPriority = newTags.indexOfFirst { it.first == RoomTag.ROOM_TAG_LOW_PRIORITY } != -1 - isServerNotice = newTags.indexOfFirst { it.first == RoomTag.ROOM_TAG_SERVER_NOTICE } != -1 + 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 @@ -170,18 +170,15 @@ internal open class RoomSummaryEntity( fun updateAliases(newAliases: List) { // only update underlying field if there is a diff - if (newAliases.toSet() != aliases.toSet()) { - Timber.w("VAL: aliases updated") + 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 = "" - set(value) { - if (value != field) field = value - } var isEncrypted: Boolean = false set(value) { 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 02ecce6afa..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 @@ -24,7 +24,7 @@ import com.zhuinden.monarchy.Monarchy import io.realm.Realm import io.realm.RealmQuery import io.realm.Sort -import org.matrix.android.sdk.api.session.room.RoomCategoryFilter +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 @@ -104,7 +104,8 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat .sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX) } - fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config): LiveData> { + fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config): LiveData> { val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> roomSummariesQuery(realm, queryParams) .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) @@ -112,12 +113,14 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat val dataSourceFactory = realmDataSourceFactory.map { roomSummaryMapper.map(it) } - return monarchy.findAllPagedWithChanges(realmDataSourceFactory, + return monarchy.findAllPagedWithChanges( + realmDataSourceFactory, LivePagedListBuilder(dataSourceFactory, pagedListConfig) ) } - fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config): UpdatableFilterLivePageResult { + fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config): UpdatableFilterLivePageResult { val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> roomSummariesQuery(realm, queryParams) .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) @@ -126,13 +129,13 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat roomSummaryMapper.map(it) } - val mapped = monarchy.findAllPagedWithChanges(realmDataSourceFactory, + val mapped = monarchy.findAllPagedWithChanges( + realmDataSourceFactory, LivePagedListBuilder(dataSourceFactory, pagedListConfig) ) return object : UpdatableFilterLivePageResult { - override val livePagedList: LiveData> - get() = mapped + override val livePagedList: LiveData> = mapped override fun updateQuery(queryParams: RoomSummaryQueryParams) { realmDataSourceFactory.updateQuery { @@ -167,10 +170,10 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat 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_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 -> { + RoomCategoryFilter.ALL -> { // nop } } @@ -182,8 +185,8 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat it.isLowPriority?.let { lp -> query.equalTo(RoomSummaryEntityFields.IS_LOW_PRIORITY, lp) } - it.isServerNotice?.let { lp -> - query.equalTo(RoomSummaryEntityFields.IS_SERVER_NOTICE, 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 69e1332dab..f254c44fda 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 @@ -117,10 +117,7 @@ internal class RoomSummaryUpdater @Inject constructor( val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel()?.aliases .orEmpty() -// roomSummaryEntity.aliases.clear() -// roomSummaryEntity.aliases.addAll(roomAliases) roomSummaryEntity.updateAliases(roomAliases) - roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|") roomSummaryEntity.isEncrypted = encryptionEvent != null roomSummaryEntity.encryptionEventTs = encryptionEvent?.originServerTs diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index 81d1fdb636..b3b18d4adc 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -19,8 +19,6 @@ package im.vector.app import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.OnLifecycleEvent -import im.vector.app.features.grouplist.SelectedGroupDataSource -import im.vector.app.features.home.room.list.ChronologicalRoomComparator import io.reactivex.disposables.CompositeDisposable import javax.inject.Inject import javax.inject.Singleton @@ -29,11 +27,10 @@ import javax.inject.Singleton * 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 selectedGroupDataSource: SelectedGroupDataSource, - private val chronologicalRoomComparator: ChronologicalRoomComparator) : LifecycleObserver { +) : LifecycleObserver { private val compositeDisposable = CompositeDisposable() 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 e3a3b7b29c..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,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScre Timber.i("onResume Fragment ${javaClass.simpleName}") } + @CallSuper override fun onPause() { super.onPause() Timber.i("onPause Fragment ${javaClass.simpleName}") @@ -154,6 +155,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScre super.onDestroyView() } + @CallSuper override fun onDestroy() { Timber.i("onDestroy Fragment ${javaClass.simpleName}") uiDisposables.dispose() 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 8a73b2e0f9..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 @@ -31,8 +31,8 @@ import im.vector.app.features.grouplist.SelectedGroupDataSource import im.vector.app.features.ui.UiStateRepository 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.RoomCategoryFilter 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 @@ -82,7 +82,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho override fun handle(action: HomeDetailAction) { when (action) { is HomeDetailAction.SwitchDisplayMode -> handleSwitchDisplayMode(action) - HomeDetailAction.MarkAllRoomsRead -> handleMarkAllRoomsRead() + HomeDetailAction.MarkAllRoomsRead -> handleMarkAllRoomsRead() } } @@ -103,12 +103,11 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho viewModelScope.launch(Dispatchers.Default) { val roomIds = session.getRoomSummaries( roomSummaryQueryParams { - this.memberships = listOf(Membership.JOIN) - this.roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS + memberships = listOf(Membership.JOIN) + roomCategoryFilter = RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS } - ).map { - it.roomId - } + ) + .map { it.roomId } try { awaitCallback { session.markAllAsRead(roomIds, it) @@ -146,7 +145,8 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho roomSummaryQueryParams { memberships = Membership.activeMemberships() } - ).asObservable() + ) + .asObservable() .throttleFirst(300, TimeUnit.MILLISECONDS) .subscribe { val dmInvites = session.getRoomSummaries( @@ -179,13 +179,13 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho setState { copy( - 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 + 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/ShortcutsHandler.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt index 99cc56bd99..00ec8b43f9 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 @@ -40,13 +40,17 @@ class ShortcutsHandler @Inject constructor( fun observeRoomsAndBuildShortcuts(): Disposable { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { // No op - return Observable.empty().subscribe() + return Disposables.empty() } - return activeSessionHolder.getSafeActiveSession()?.getPagedRoomSummariesLive(roomSummaryQueryParams { - this.memberships = listOf(Membership.JOIN) - this.roomTagQueryFilter = RoomTagQueryFilter(isFavorite = true, null, null) - })?.asObservable() + return activeSessionHolder.getSafeActiveSession() + ?.getPagedRoomSummariesLive( + roomSummaryQueryParams { + memberships = listOf(Membership.JOIN) + roomTagQueryFilter = RoomTagQueryFilter(isFavorite = true, null, null) + } + ) + ?.asObservable() ?.subscribe { rooms -> val shortcuts = rooms .take(n = 4) // Android only allows us to create 4 shortcuts @@ -55,7 +59,6 @@ class ShortcutsHandler @Inject constructor( ShortcutManagerCompat.removeAllDynamicShortcuts(context) ShortcutManagerCompat.addDynamicShortcuts(context, shortcuts) } - ?: Disposables.empty() } 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 a5c071680d..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,7 +22,7 @@ import org.matrix.android.sdk.api.session.room.notification.RoomNotificationStat sealed class RoomListAction : VectorViewModelAction { data class SelectRoom(val roomSummary: RoomSummary) : RoomListAction() - data class ToggleSection(val section: RoomListViewModel.RoomsSection) : 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() 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 8323c3b155..8e837e85c6 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 @@ -95,7 +95,7 @@ class RoomListFragment @Inject constructor( ) private val adapterInfosList = mutableListOf() - private var concatAdapter: ConcatAdapter? = null + private val concatAdapter = ConcatAdapter() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -104,10 +104,10 @@ class RoomListFragment @Inject constructor( sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) roomListViewModel.observeViewEvents { when (it) { - is RoomListViewEvents.Loading -> showLoading(it.message) - is RoomListViewEvents.Failure -> showFailure(it.throwable) + is RoomListViewEvents.Loading -> showLoading(it.message) + is RoomListViewEvents.Failure -> showFailure(it.throwable) is RoomListViewEvents.SelectRoom -> handleSelectRoom(it) - is RoomListViewEvents.Done -> Unit + is RoomListViewEvents.Done -> Unit }.exhaustive } @@ -134,10 +134,10 @@ class RoomListFragment @Inject constructor( val isRoomSectionExpanded = roomsSection.isExpanded.value.orTrue() if (actualBlock.section.isExpanded && !isRoomSectionExpanded) { // we have to remove the content adapter - concatAdapter?.removeAdapter(actualBlock.contentAdapter.adapter) + concatAdapter.removeAdapter(actualBlock.contentAdapter.adapter) } else if (!actualBlock.section.isExpanded && isRoomSectionExpanded) { // we must add it back! - concatAdapter?.addAdapter(contentInsertIndex, actualBlock.contentAdapter.adapter) + concatAdapter.addAdapter(contentInsertIndex, actualBlock.contentAdapter.adapter) } contentInsertIndex = if (isRoomSectionExpanded) { contentInsertIndex + 2 @@ -148,7 +148,7 @@ class RoomListFragment @Inject constructor( isExpanded = isRoomSectionExpanded ) actualBlock.headerHeaderAdapter.updateSection( - actualBlock.headerHeaderAdapter.section.copy(isExpanded = isRoomSectionExpanded) + actualBlock.headerHeaderAdapter.roomsSectionData.copy(isExpanded = isRoomSectionExpanded) ) } } @@ -160,16 +160,10 @@ class RoomListFragment @Inject constructor( override fun onDestroyView() { adapterInfosList.onEach { it.contentAdapter.removeModelBuildListener(modelBuildListener) } adapterInfosList.clear() -// roomController.removeModelBuildListener(modelBuildListener) modelBuildListener = null views.roomListView.cleanup() -// controllers.onEach { -// it.listener = null -// // concatAdapter.removeAdapter(it.adapter) -// } -// controllers.clear() -// roomController.listener = null -// favRoomController.listener = null + footerController.listener = null + // TODO Cleanup listener on the ConcatAdapter's adapters? stateRestorer.clear() views.createChatFabMenu.listener = null super.onDestroyView() @@ -182,8 +176,8 @@ class RoomListFragment @Inject constructor( private fun setupCreateRoomButton() { when (roomListParams.displayMode) { RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.isVisible = true - RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.isVisible = true - RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.isVisible = true + RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.isVisible = true + RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.isVisible = true else -> Unit // No button in this mode } @@ -238,40 +232,35 @@ class RoomListFragment @Inject constructor( stateRestorer = LayoutManagerStateRestorer(layoutManager).register() views.roomListView.layoutManager = layoutManager views.roomListView.itemAnimator = RoomListAnimator() -// views.roomListView.setRecycledViewPool(sharedViewPool) layoutManager.recycleChildrenOnDetach = true modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) } - val concatAdapter = ConcatAdapter() -// val hasOnlyOneSection = roomListViewModel.sections.size == 1 roomListViewModel.sections.forEach { section -> - val sectionAdapter = SectionHeaderAdapter { roomListViewModel.handle(RoomListAction.ToggleSection(section)) }.also { - it.updateSection(SectionHeaderAdapter.SectionViewModel( - section.sectionName - )) + it.updateSection(SectionHeaderAdapter.RoomsSectionData(section.sectionName)) } - val contentAdapter = pagedControllerFactory.createRoomSummaryPagedController().also { - section.livePages.observe(viewLifecycleOwner) { pl -> - it.submitList(pl) - sectionAdapter.updateSection(sectionAdapter.section.copy(isHidden = pl.isEmpty())) - checkEmptyState() - } - section.notificationCount.observe(viewLifecycleOwner) { counts -> - sectionAdapter.updateSection(sectionAdapter.section.copy( - notificationCount = counts.totalCount(), - isHighlighted = counts.isHighlight() - )) - } - section.isExpanded.observe(viewLifecycleOwner) { _ -> - refreshCollapseStates() - } - it.listener = this - } + 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( @@ -288,10 +277,9 @@ class RoomListFragment @Inject constructor( } // Add the footer controller - this.footerController.listener = this + footerController.listener = this concatAdapter.addAdapter(footerController.adapter) - this.concatAdapter = concatAdapter views.roomListView.adapter = concatAdapter } @@ -299,8 +287,8 @@ class RoomListFragment @Inject constructor( if (isAdded) { when (roomListParams.displayMode) { RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.show() - RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.show() - RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.show() + RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.show() + RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.show() else -> Unit } } @@ -308,28 +296,28 @@ class RoomListFragment @Inject constructor( private fun handleQuickActions(quickAction: RoomListQuickActionsSharedAction) { when (quickAction) { - is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> { + is RoomListQuickActionsSharedAction.NotificationsAllNoisy -> { roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.ALL_MESSAGES_NOISY)) } - is RoomListQuickActionsSharedAction.NotificationsAll -> { + is RoomListQuickActionsSharedAction.NotificationsAll -> { roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.ALL_MESSAGES)) } is RoomListQuickActionsSharedAction.NotificationsMentionsOnly -> { roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.MENTIONS_ONLY)) } - is RoomListQuickActionsSharedAction.NotificationsMute -> { + is RoomListQuickActionsSharedAction.NotificationsMute -> { roomListViewModel.handle(RoomListAction.ChangeRoomNotificationState(quickAction.roomId, RoomNotificationState.MUTE)) } - is RoomListQuickActionsSharedAction.Settings -> { + is RoomListQuickActionsSharedAction.Settings -> { navigator.openRoomProfile(requireActivity(), quickAction.roomId) } - is RoomListQuickActionsSharedAction.Favorite -> { + is RoomListQuickActionsSharedAction.Favorite -> { roomListViewModel.handle(RoomListAction.ToggleTag(quickAction.roomId, RoomTag.ROOM_TAG_FAVOURITE)) } - is RoomListQuickActionsSharedAction.LowPriority -> { + is RoomListQuickActionsSharedAction.LowPriority -> { roomListViewModel.handle(RoomListAction.ToggleTag(quickAction.roomId, RoomTag.ROOM_TAG_LOW_PRIORITY)) } - is RoomListQuickActionsSharedAction.Leave -> { + is RoomListQuickActionsSharedAction.Leave -> { promptLeaveRoom(quickAction.roomId) } }.exhaustive @@ -364,7 +352,7 @@ class RoomListFragment @Inject constructor( } private fun checkEmptyState() { - val hasNoRoom = adapterInfosList.all { it.headerHeaderAdapter.section.isHidden } + val hasNoRoom = adapterInfosList.all { it.headerHeaderAdapter.roomsSectionData.isHidden } if (hasNoRoom) { val emptyState = when (roomListParams.displayMode) { RoomListDisplayMode.NOTIFICATIONS -> { @@ -373,14 +361,14 @@ class RoomListFragment @Inject constructor( image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_noun_party_popper), message = getString(R.string.room_list_catchup_empty_body)) } - RoomListDisplayMode.PEOPLE -> + 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 -> + RoomListDisplayMode.ROOMS -> StateView.State.Empty( title = getString(R.string.room_list_rooms_empty_title), image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_room), 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 2a214e6e90..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 @@ -17,10 +17,7 @@ package im.vector.app.features.home.room.list import androidx.annotation.StringRes -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope -import androidx.paging.PagedList import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.ViewModelContext @@ -34,27 +31,26 @@ 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.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.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.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.rx.asObservable import org.matrix.android.sdk.rx.rx import timber.log.Timber import javax.inject.Inject -class RoomListViewModel @Inject constructor(initialState: RoomListViewState, - private val session: Session, - private val stringProvider: StringProvider) - : 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 @@ -84,19 +80,9 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } } - 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 - ) - 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 @@ -113,7 +99,6 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, 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 @@ -143,18 +128,20 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, 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 { livePagedList -> - updatableQuery = livePagedList - sections.add(RoomsSection(name, livePagedList.livePagedList)) - } - } + 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 @@ -171,14 +158,14 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, override fun handle(action: RoomListAction) { when (action) { - is RoomListAction.SelectRoom -> handleSelectRoom(action) - is RoomListAction.AcceptInvitation -> handleAcceptInvitation(action) - is RoomListAction.RejectInvitation -> handleRejectInvitation(action) - is RoomListAction.FilterWith -> handleFilter(action) - is RoomListAction.LeaveRoom -> handleLeaveRoom(action) + is RoomListAction.SelectRoom -> handleSelectRoom(action) + is RoomListAction.AcceptInvitation -> handleAcceptInvitation(action) + is RoomListAction.RejectInvitation -> handleRejectInvitation(action) + is RoomListAction.FilterWith -> handleFilter(action) + is RoomListAction.LeaveRoom -> handleLeaveRoom(action) is RoomListAction.ChangeRoomNotificationState -> handleChangeNotificationMode(action) - is RoomListAction.ToggleTag -> handleToggleTag(action) - is RoomListAction.ToggleSection -> handleToggleSection(action.section) + is RoomListAction.ToggleTag -> handleToggleTag(action) + is RoomListAction.ToggleSection -> handleToggleSection(action.section) }.exhaustive } @@ -186,40 +173,41 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, @StringRes nameRes: Int, notifyOfLocalEcho: Boolean = false, query: (RoomSummaryQueryParams.Builder) -> Unit) { - withQueryParams({ - query.invoke(it) - }) { roomQueryParams -> + withQueryParams( + { query.invoke(it) }, + { roomQueryParams -> - val name = stringProvider.getString(nameRes) - session.getPagedRoomSummariesLive(roomQueryParams) - .let { livePagedList -> + 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() + // 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 + 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) - } + RoomSummaryQueryParams.Builder() + .apply { builder.invoke(this) } + .build() + .let { block(it) } } fun isPublicRoom(roomId: String): Boolean { @@ -233,10 +221,13 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } 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) { @@ -247,8 +238,8 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, } updatableQuery?.updateQuery( roomSummaryQueryParams { - this.memberships = Membership.activeMemberships() - this.displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) + memberships = Membership.activeMemberships() + displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) } ) } @@ -351,7 +342,7 @@ class RoomListViewModel @Inject constructor(initialState: RoomListViewState, private fun String.otherTag(): String? { return when (this) { - RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY + RoomTag.ROOM_TAG_FAVOURITE -> RoomTag.ROOM_TAG_LOW_PRIORITY RoomTag.ROOM_TAG_LOW_PRIORITY -> RoomTag.ROOM_TAG_FAVOURITE else -> null } 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 index 38137e0033..20386d739a 100644 --- 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 @@ -23,16 +23,19 @@ 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) { +class RoomSummaryPagedControllerFactory @Inject constructor( + private val roomSummaryItemFactory: RoomSummaryItemFactory +) { fun createRoomSummaryPagedController(): RoomSummaryPagedController { return RoomSummaryPagedController(roomSummaryItemFactory) } } -class RoomSummaryPagedController constructor(private val roomSummaryItemFactory: RoomSummaryItemFactory) - : PagedListEpoxyController( -// Important it must match the PageList builder notify Looper +class RoomSummaryPagedController( + private val roomSummaryItemFactory: RoomSummaryItemFactory +) : PagedListEpoxyController( + // Important it must match the PageList builder notify Looper modelBuildingHandler = createUIHandler() ) { @@ -46,28 +49,20 @@ class RoomSummaryPagedController constructor(private val roomSummaryItemFactory: } override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> { - val unwrappedItem = item // for place holder if enabled - ?: return roomSummaryItemFactory.createRoomItem( - RoomSummary( - roomId = "null_item_pos_$currentPosition", - name = "", - encryptionEventTs = null, - isEncrypted = false, - typingUsers = emptyList() - ), emptySet(), null, null) + item ?: return roomSummaryItemFactory.createRoomItem( + roomSummary = RoomSummary( + roomId = "null_item_pos_$currentPosition", + name = "", + encryptionEventTs = null, + isEncrypted = false, + typingUsers = emptyList() + ), + selectedRoomIds = emptySet(), + onClick = null, + onLongClick = null + ) -// GenericItem_().apply { id("null_item_pos_$currentPosition") } - - return roomSummaryItemFactory.create(unwrappedItem, roomChangeMembershipStates ?: emptyMap(), emptySet(), listener) + return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), listener) } - -// override fun onModelBound(holder: EpoxyViewHolder, boundModel: EpoxyModel<*>, position: Int, previouslyBoundModel: EpoxyModel<*>?) { -// Timber.w("VAL: Will load around $position") -// super.onModelBound(holder, boundModel, position, previouslyBoundModel) -// } -// fun onRoomLongClicked() { -// userPreferencesProvider.neverShowLongClickOnRoomHelpAgain() -// requestModelBuild() -// } } 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 index b137947ef7..f9c5766821 100644 --- 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 @@ -30,7 +30,7 @@ class SectionHeaderAdapter constructor( private val onClickAction: (() -> Unit) ) : RecyclerView.Adapter() { - data class SectionViewModel( + data class RoomsSectionData( val name: String, val isExpanded: Boolean = true, val notificationCount: Int = 0, @@ -38,12 +38,12 @@ class SectionHeaderAdapter constructor( val isHidden: Boolean = true ) - lateinit var section: SectionViewModel + lateinit var roomsSectionData: RoomsSectionData private set - fun updateSection(newSection: SectionViewModel) { - if (!::section.isInitialized || newSection != section) { - section = newSection + fun updateSection(newRoomsSectionData: RoomsSectionData) { + if (!::roomsSectionData.isInitialized || newRoomsSectionData != roomsSectionData) { + roomsSectionData = newRoomsSectionData notifyDataSetChanged() } } @@ -52,7 +52,7 @@ class SectionHeaderAdapter constructor( setHasStableIds(true) } - override fun getItemId(position: Int) = section.hashCode().toLong() + override fun getItemId(position: Int) = roomsSectionData.hashCode().toLong() override fun getItemViewType(position: Int) = R.layout.item_room_category @@ -61,10 +61,10 @@ class SectionHeaderAdapter constructor( } override fun onBindViewHolder(holder: VH, position: Int) { - holder.bind(section) + holder.bind(roomsSectionData) } - override fun getItemCount(): Int = if (section.isHidden) 0 else 1 + override fun getItemCount(): Int = if (roomsSectionData.isHidden) 0 else 1 class VH constructor( private val binding: ItemRoomCategoryBinding, @@ -77,14 +77,14 @@ class SectionHeaderAdapter constructor( })) } - fun bind(section: SectionViewModel) { - binding.roomCategoryTitleView.text = section.name + fun bind(roomsSectionData: RoomsSectionData) { + binding.roomCategoryTitleView.text = roomsSectionData.name val tintColor = ThemeUtils.getColor(binding.root.context, R.attr.riotx_text_secondary) - val expandedArrowDrawableRes = if (section.isExpanded) R.drawable.ic_expand_more_white else R.drawable.ic_expand_less_white + 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(section.notificationCount, section.isHighlighted)) + binding.roomCategoryUnreadCounterBadgeView.render(UnreadCounterBadgeView.State(roomsSectionData.notificationCount, roomsSectionData.isHighlighted)) binding.roomCategoryTitleView.setCompoundDrawablesWithIntrinsicBounds(null, null, expandedArrowDrawable, null) } From fcc635ac8f845a2abc8b031304cb8037afa86dbc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Apr 2021 07:19:53 +0000 Subject: [PATCH 171/249] Bump moshi_version from 1.11.0 to 1.12.0 Bumps `moshi_version` from 1.11.0 to 1.12.0. Updates `moshi-adapters` from 1.11.0 to 1.12.0 - [Release notes](https://github.com/square/moshi/releases) - [Changelog](https://github.com/square/moshi/blob/master/CHANGELOG.md) - [Commits](https://github.com/square/moshi/compare/moshi-parent-1.11.0...parent-1.12.0) Updates `moshi-kotlin-codegen` from 1.11.0 to 1.12.0 - [Release notes](https://github.com/square/moshi/releases) - [Changelog](https://github.com/square/moshi/blob/master/CHANGELOG.md) - [Commits](https://github.com/square/moshi/compare/moshi-parent-1.11.0...parent-1.12.0) Signed-off-by: dependabot[bot] --- matrix-sdk-android/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 0ca605dc8b..f9f7b2e4e4 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -108,7 +108,7 @@ 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' diff --git a/vector/build.gradle b/vector/build.gradle index bf164facee..6e662d35f8 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -296,7 +296,7 @@ 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 moshi_version = '1.12.0' def daggerVersion = '2.33' def autofill_version = "1.1.0" def work_version = '2.5.0' From 100b187be37ba8cefe49d2cca2feb7f3f014df8e Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 2 Apr 2021 11:43:56 +0200 Subject: [PATCH 172/249] Fix / loading initial state, duplicate sections and footer on empty --- .../app/features/home/room/list/RoomListFragment.kt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) 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 8e837e85c6..ede3df2c18 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 @@ -95,11 +95,12 @@ class RoomListFragment @Inject constructor( ) private val adapterInfosList = mutableListOf() - private val concatAdapter = ConcatAdapter() + private var concatAdapter : ConcatAdapter? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setupCreateRoomButton() + views.stateView.contentView = views.roomListView + views.stateView.state = StateView.State.Loading setupRecyclerView() sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) roomListViewModel.observeViewEvents { @@ -134,10 +135,10 @@ class RoomListFragment @Inject constructor( val isRoomSectionExpanded = roomsSection.isExpanded.value.orTrue() if (actualBlock.section.isExpanded && !isRoomSectionExpanded) { // we have to remove the content adapter - concatAdapter.removeAdapter(actualBlock.contentAdapter.adapter) + concatAdapter?.removeAdapter(actualBlock.contentAdapter.adapter) } else if (!actualBlock.section.isExpanded && isRoomSectionExpanded) { // we must add it back! - concatAdapter.addAdapter(contentInsertIndex, actualBlock.contentAdapter.adapter) + concatAdapter?.addAdapter(contentInsertIndex, actualBlock.contentAdapter.adapter) } contentInsertIndex = if (isRoomSectionExpanded) { contentInsertIndex + 2 @@ -166,6 +167,7 @@ class RoomListFragment @Inject constructor( // TODO Cleanup listener on the ConcatAdapter's adapters? stateRestorer.clear() views.createChatFabMenu.listener = null + concatAdapter = null super.onDestroyView() } @@ -236,6 +238,8 @@ class RoomListFragment @Inject constructor( modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) } + val concatAdapter = ConcatAdapter() + roomListViewModel.sections.forEach { section -> val sectionAdapter = SectionHeaderAdapter { roomListViewModel.handle(RoomListAction.ToggleSection(section)) @@ -280,6 +284,7 @@ class RoomListFragment @Inject constructor( footerController.listener = this concatAdapter.addAdapter(footerController.adapter) + this.concatAdapter = concatAdapter views.roomListView.adapter = concatAdapter } From 48292982555a8f84bae42ac6b45d6c88419c6a48 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 2 Apr 2021 11:44:10 +0200 Subject: [PATCH 173/249] ktlint --- .../android/sdk/internal/database/model/RoomSummaryEntity.kt | 1 - .../android/sdk/internal/session/room/DefaultRoomService.kt | 1 - vector/src/main/java/im/vector/app/AppStateHandler.kt | 3 +-- .../main/java/im/vector/app/features/home/ShortcutsHandler.kt | 1 - 4 files changed, 1 insertion(+), 5 deletions(-) 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 669074ffd2..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 @@ -25,7 +25,6 @@ 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 org.matrix.android.sdk.api.session.room.model.tag.RoomTag -import timber.log.Timber internal open class RoomSummaryEntity( @PrimaryKey var roomId: String = "" 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 f1d4bfca3f..bd63ba480e 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 @@ -195,4 +195,3 @@ internal class DefaultRoomService @Inject constructor( .executeBy(taskExecutor) } } - diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index b3b18d4adc..edec704f18 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -29,8 +29,7 @@ import javax.inject.Singleton */ // TODO Keep this class for now, will maybe be used fro Space @Singleton -class AppStateHandler @Inject constructor( -) : LifecycleObserver { +class AppStateHandler @Inject constructor() : LifecycleObserver { private val compositeDisposable = CompositeDisposable() 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 00ec8b43f9..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 @@ -22,7 +22,6 @@ import android.os.Build import androidx.core.content.getSystemService import androidx.core.content.pm.ShortcutManagerCompat import im.vector.app.core.di.ActiveSessionHolder -import io.reactivex.Observable import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposables import org.matrix.android.sdk.api.query.RoomTagQueryFilter From d49ed63f1d2e17beb8bb53b189d7cad71223d268 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 2 Apr 2021 11:56:38 +0200 Subject: [PATCH 174/249] fix / put back setup button --- .../im/vector/app/features/home/room/list/RoomListFragment.kt | 1 + 1 file changed, 1 insertion(+) 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 ede3df2c18..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 @@ -101,6 +101,7 @@ class RoomListFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) views.stateView.contentView = views.roomListView views.stateView.state = StateView.State.Loading + setupCreateRoomButton() setupRecyclerView() sharedActionViewModel = activityViewModelProvider.get(RoomListQuickActionsSharedActionViewModel::class.java) roomListViewModel.observeViewEvents { From bd14c77ff08c8940cdd2d77b39c88f81d55ac16a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 2 Apr 2021 11:43:52 +0200 Subject: [PATCH 175/249] Improve room name for invitation --- .../database/model/RoomMemberSummaryEntity.kt | 2 ++ .../room/membership/RoomDisplayNameResolver.kt | 18 +++++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) 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/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt index 0e18e30b13..f19862b397 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 @@ -77,14 +77,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 @@ -150,7 +150,7 @@ internal class RoomDisplayNameResolver @Inject constructor( if (roomMemberSummary == null) return null val isUnique = roomMemberHelper.isUniqueDisplayName(roomMemberSummary.displayName) return if (isUnique) { - roomMemberSummary.displayName + roomMemberSummary.getBestName() } else { "${roomMemberSummary.displayName} (${roomMemberSummary.userId})" } From a4348b8194951a48c9d12bbd9ef49e4524334ee1 Mon Sep 17 00:00:00 2001 From: Yuriy Bulka Date: Fri, 2 Apr 2021 19:24:21 +0000 Subject: [PATCH 176/249] Translated using Weblate (Ukrainian) Currently translated at 73.7% (1743 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- vector/src/main/res/values-uk/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index a9ad29004f..0a4c3b9d19 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -1769,4 +1769,5 @@ Це не загальнодоступна кімната. Ви не зможете знову приєднатися без запрошення. У вас немає дозволу на ввімкнення шифрування в цій кімнаті. Дозволи кімнати + Ознайомтеся з непрочитаними повідомленнями тут \ No newline at end of file From 1ebcafd920dcdf58e28ddc09747ab8cb2c767396 Mon Sep 17 00:00:00 2001 From: strix aluco Date: Fri, 2 Apr 2021 15:55:08 +0000 Subject: [PATCH 177/249] Translated using Weblate (Ukrainian) Currently translated at 73.7% (1743 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- vector/src/main/res/values-uk/strings.xml | 476 +++++++++++++++++----- 1 file changed, 382 insertions(+), 94 deletions(-) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 0a4c3b9d19..f3a9be7023 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -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,7 +869,7 @@ Завантажити Говорити Очистити - Вдруге запитати ключі шифрування з інших ваших пристроїв. + Вдруге запитати ключі шифрування з інших ваших сеансів. Запит ключа відправлений. Запит відправлений Вібрація при згадуванні користувача @@ -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,11 +1701,11 @@ Повідомлення тут не захищено наскрізним шифруванням. Увімкнути наскрізне шифрування… Повідомлення в цій кімнаті наскрізно зашифровані. - Перевірити цей сеанс + Звірити цей сеанс Перевірте цей сеанс, підтвердивши, що на екрані партнера з’являються такі цифри Перевірте цей сеанс, підтвердивши, що на екрані партнера з’являються ці емоджі - Перевірте цей сеанс, щоб позначити його надійним. Довірені сеанси партнерів дають вам додаткову впевненість під час використання наскрізно зашифрованих повідомлень. - Перевірте цей сеанс, щоб позначити його надійним та надати йому доступ до зашифрованих повідомлень. Якщо ви не входили в цей сеанс, ваш обліковий запис може бути зламано: + Звірте цей сеанс, щоб позначити його надійним. Довірені сеанси партнерів дають вам додаткову впевненість під час використання наскрізно зашифрованих повідомлень. + Звірте цей сеанс, щоб позначити його надійним та надати йому доступ до зашифрованих повідомлень. Якщо ви не входили в цей сеанс, ваш обліковий запис може бути зламано: Назва або ID (#example:matrix.org) Назва Фільтрувати за іменем користувача або ID… @@ -1749,7 +1753,7 @@ Вмикати шифрування кімнати Змінювати основну адресу кімнати Змінювати аватар кімнати - Змінювати віджети + Змінювати знадоби Сповіщати всіх Вилучати повідомлення, надіслані іншими Блокувати користувачів @@ -1770,4 +1774,288 @@ У вас немає дозволу на ввімкнення шифрування в цій кімнаті. Дозволи кімнати Ознайомтеся з непрочитаними повідомленнями тут + Ви впевнені, що бажаєте видалити усі не надіслані повідомлення з цієї кімнати\? + Посилання %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} Desktop, ${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 From d5d397d5ff9f7fb837ab87db6401c2b194561477 Mon Sep 17 00:00:00 2001 From: Vivek K J Date: Fri, 2 Apr 2021 12:10:17 +0000 Subject: [PATCH 178/249] Translated using Weblate (Malayalam) Currently translated at 20.9% (494 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ml/ --- vector/src/main/res/values-ml/strings.xml | 84 +++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/vector/src/main/res/values-ml/strings.xml b/vector/src/main/res/values-ml/strings.xml index 17e8c5cf7b..34d67270b3 100644 --- a/vector/src/main/res/values-ml/strings.xml +++ b/vector/src/main/res/values-ml/strings.xml @@ -430,4 +430,88 @@ നിങ്ങളുടെ ക്ഷണം %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 ചർച്ച സൃഷ്ടിച്ചു \ No newline at end of file From 99eb5bd533bc315ea34134483dfa681fee475bb4 Mon Sep 17 00:00:00 2001 From: Safa Alfulaij Date: Thu, 1 Apr 2021 19:20:56 +0000 Subject: [PATCH 179/249] Translated using Weblate (Arabic) Currently translated at 38.6% (913 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ar/ --- vector/src/main/res/values-ar/strings.xml | 66 +++++++++++------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/vector/src/main/res/values-ar/strings.xml b/vector/src/main/res/values-ar/strings.xml index aac9d1b6cd..bc0738643f 100644 --- a/vector/src/main/res/values-ar/strings.xml +++ b/vector/src/main/res/values-ar/strings.xml @@ -1,17 +1,17 @@ - أرسلَ %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⁩ اسم العرض على إنَّ %1$s قد غيَّرَ اسمه الظاهر من %2$s إلى %3$s إنَّ %1$s قد أزالَ اسمه الظاهر (لقد كان %2$s) إنَّ %1$s قد غيَّرَ الموضوع إلى: %2$s @@ -42,12 +42,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 +61,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 +140,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 From d492569539f30d24d3cc5286d9f9c8d3b4e9d11a Mon Sep 17 00:00:00 2001 From: libexus Date: Thu, 1 Apr 2021 09:56:22 +0000 Subject: [PATCH 180/249] Translated using Weblate (German) Currently translated at 99.9% (2361 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- vector/src/main/res/values-de/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index 6ad2fe92f7..52e67eb460 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -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. @@ -2729,7 +2729,7 @@ 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 - Schlug fehl + Fehlgeschlagen Willst du zu sendende Nachrichten zurückziehen\? Alle fehgeschlagene Nachrichten löschen Senden der Nachricht gescheitert From 77fb8de71ff9270c5108ed7866261f3262c78461 Mon Sep 17 00:00:00 2001 From: Safa Alfulaij Date: Thu, 1 Apr 2021 19:00:23 +0000 Subject: [PATCH 181/249] Translated using Weblate (Arabic) Currently translated at 92.3% (12 of 13 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/ar/ --- fastlane/metadata/android/ar/changelogs/40101010.txt | 2 ++ fastlane/metadata/android/ar/title.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 fastlane/metadata/android/ar/changelogs/40101010.txt 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 سابقًا) From 472e7b8246326df6ceae1a3e2a506144c3914132 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Fri, 2 Apr 2021 20:58:52 +0000 Subject: [PATCH 182/249] Translated using Weblate (Ukrainian) Currently translated at 73.7% (1743 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- vector/src/main/res/values-uk/strings.xml | 54 +++++++++++------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index f3a9be7023..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 ** Пристрій відправника не надіслав нам ключ для цього повідомлення. - Неможливо відредагувати + Не вдається редагувати Не вдалося надіслати повідомлення Не вдалося завантажити зображення Помилка мережі @@ -1779,7 +1779,7 @@ \n \nВи впевнені, що бажаєте продовжити\? Ви вийшли - Видалити… + Вилучити… Наліпка Використовувати ботів, мости, знадоби та пакунки наліпок Зв\'язок із сервером втрачено @@ -1822,31 +1822,31 @@ Надсилає вказане повідомлення зі снігом Надсилає вказане повідомлення з конфетті - Показати пристрій, з якого ви можете звірити цю сесію просто зараз - Показати %d пристрої, з яких ви можете звірити цю сесію просто зараз - Показати %d пристроїв, з яких ви можете звірити цю сесію просто зараз - Показати %d пристроїв, з яких ви можете звірити цю сесію просто зараз + Показати пристрій, з якого ви можете звірити цей сеанс просто зараз + Показати %d пристрої, з яких ви можете звірити цей сеанс просто зараз + Показати %d пристроїв, з яких ви можете звірити цей сеанс просто зараз + Показати %d пристроїв, з яких ви можете звірити цей сеанс просто зараз Ви розпочнете знову, але без історії повідомлень, без довірених пристроїв та користувачів Вдавайтесь до цього лише за умов відсутності жодного пристрою, з якого ви можете звірити поточний пристрій. Якщо ви скинете все Скинути все - Резервна копія не може бути дешифрована цією парольною фразою: переконайтесь, будь ласка, що відновлювальна парольна фраза зазначена правильно. + Резервна копія не може бути дешифрована цією парольною фразою: переконайтесь, що відновлювальна парольна фраза зазначена правильно. Не знаєте вашої відновлювальної парольної фрази\? Ви можете %s. Відновлювальна парольна фраза Забули або втратили усі можливості для відновлення\? Скинути все Використати файл Скористатись відновлювальними парольною фразою або ключем Скористатись відновлювальними парольною фразою або ключем - Використовуйте найостаннішій ${app_name} на ваших інших пристроях, ${app_name} Web, ${app_name} Desktop, ${app_name} iOS, ${app_name} для Android, або будь-який інший, здатний до перехресного підписування, Matrix-клієнт + Використовуйте найостаннішій ${app_name} на ваших інших пристроях, ${app_name} Web, ${app_name} для комп\'ютерів, ${app_name} iOS, ${app_name} для Android, або будь-який інший, здатний до перехресного підписування, Matrix-клієнт Використовуйте найостаннішій ${app_name} на ваших інших пристроях: - Якщо ви не можете доступитись до чинної сесії - Використайте чинну сесію задля звірки цієї сесії, надавши їй таким чином доступ до зашифрованих повідомлень. + Якщо ви не можете доступитись до чинного сеансу + Використайте чинний сеанс, щоб звірити цей сеанс, таким чином надавши йому доступ до зашифрованих повідомлень. Підтвердьте вашу тотожність, звіривши цей вхід та надавши йому доступ до зашифрованих повідомлень. Звірте новий вхід, що доступається до вашого облікового запису: %1$s Звірте цей вхід Очікування… - Тицьніть, щоб переглянути та звірити + Торкніться, щоб переглянути та звірити Новий вхід. Це були ви\? Оновити Скинути ключі @@ -1869,7 +1869,7 @@ %1$s надсилає %2$s запрошення приєднатись до кімнати. Причина: %3$s Ви надіслали %1$s запрошення приєднатись до кімнати. Причина: %2$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. Причина: %3$s Ви викинули %1$s. Причина: %2$s @@ -1883,9 +1883,9 @@ \n \nВаші повідомлення захищені замками, тож лише ви та отримувачі мають унікальні ключі для їхнього відмикання. Це початок %s. - %1$s відхилили цей виклик + %1$s відхиляє цей виклик Ви відхилили цей виклик %1$s - %1$s розпочали виклик + %1$s розпочинає виклик Ви розпочали виклик Швидкі реакції Користувачі @@ -1899,10 +1899,10 @@ %s утримали виклик Утримати - Поточний виклик - %1$d поточні виклики - %1$d поточних викликів - %1$d поточних викликів + Призупинений виклик + %1$d призупинені виклики + %1$d призупинених викликів + %1$d призупинених викликів Поточний виклик (%1$s) @@ -1916,7 +1916,7 @@ Додати за matrix ID Push-сповіщення вимкнено Не вдалось розблокувати користувача - Заблоковано від %1$s + Блокування від %1$s Відкликати запрошення для %1$s\? Відкликати запрошення Введіть ваш %s, щоб продовжити. @@ -1942,7 +1942,7 @@ Початкова синхронізація: \nЗвантаження даних… Початкова синхронізація: -\nОчікування на відповідь сервера… +\nОчікування відповіді сервера… Запросити користувачів ЗАПРОСИТИ Розпочніть друкувати, щоб отримати результати @@ -1953,9 +1953,9 @@ Пошук контактів у Matrix Контакти Отримання контактів… - QR-код не відскановано! - QR-код не є дійсним (недійсний URI)! - Цей QR-код не є дійсним + QR-код не зіскановано! + QR-код не дійсний (недійсний URI)! + Цей QR-код не дійсний Чекаємо на %s… Майже все! Чекаємо на підтвердження… Майже все! Чи показує інший пристрій такий самий щит\? @@ -1967,12 +1967,12 @@ QR-код Пошук за іменем або ID Додати за QR-кодом - Чекання + Очікування Зазначте адресу сервера ідентифікації В іншому разі, ви можете зазначити адресу будь-якого іншого сервера ідентифікації Ваш домашній сервер (%1$s) пропонує використовувати %2$s як ваш сервер ідентифікації - Будь ласка, погодьтесь спочатку з умовами використання сервера ідентифікації у налаштуваннях. - Будь ласка, налаштуйте спочатку сервер ідентифікації. + Спочатку погодьтесь з умовами користування сервером ідентифікації в налаштуваннях. + Спочатку налаштуйте сервер ідентифікації. Цей сервер ідентифікації є застарілим. ${app_name} підтримує лише API V2. Від\'єднатись від сервера ідентифікації %s\? Погодьтесь з умовами використання сервера ідентифікації (%s), щоб дозволити вашу виявність за електронною адресою та номером телефону. @@ -2003,7 +2003,7 @@ Зреагувати Завершити Відповісти - Відредагувати + Редагувати Зрозуміло Riot тепер називається Element! Файл %1$s було звантажено! From 1612d57aab5c52ee408275aecc3318e8d5b7cff4 Mon Sep 17 00:00:00 2001 From: Vivek K J Date: Sat, 3 Apr 2021 12:04:49 +0000 Subject: [PATCH 183/249] Translated using Weblate (Malayalam) Currently translated at 25.0% (592 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ml/ --- vector/src/main/res/values-ml/strings.xml | 126 ++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/vector/src/main/res/values-ml/strings.xml b/vector/src/main/res/values-ml/strings.xml index 34d67270b3..e11aaa7639 100644 --- a/vector/src/main/res/values-ml/strings.xml +++ b/vector/src/main/res/values-ml/strings.xml @@ -514,4 +514,130 @@ %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 From ef1265fe3893d293cce9c042d0054499c0100a22 Mon Sep 17 00:00:00 2001 From: Andrejs Date: Sat, 3 Apr 2021 19:41:19 +0000 Subject: [PATCH 184/249] Translated using Weblate (Latvian) Currently translated at 66.8% (1580 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/lv/ --- vector/src/main/res/values-lv/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vector/src/main/res/values-lv/strings.xml b/vector/src/main/res/values-lv/strings.xml index 01646013ff..ee98765a7c 100644 --- a/vector/src/main/res/values-lv/strings.xml +++ b/vector/src/main/res/values-lv/strings.xml @@ -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 @@ -691,7 +691,7 @@ 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 From ee77ed134bdd17839034e69f02ee5d6f7df0c05d Mon Sep 17 00:00:00 2001 From: Hakim Oubouali Date: Sun, 4 Apr 2021 01:02:11 +0000 Subject: [PATCH 185/249] Translated using Weblate (Central Atlas Tamazight) Currently translated at 3.5% (83 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/tzm/ --- vector/src/main/res/values-tzm/strings.xml | 55 ++++++++++++++++++++++ 1 file changed, 55 insertions(+) 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 From 0addb3aeeeec1bfc1b17c6ee9bf7967fb066c2e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Apr 2021 07:20:12 +0000 Subject: [PATCH 186/249] Bump libphonenumber from 8.12.20 to 8.12.21 Bumps [libphonenumber](https://github.com/google/libphonenumber) from 8.12.20 to 8.12.21. - [Release notes](https://github.com/google/libphonenumber/releases) - [Changelog](https://github.com/google/libphonenumber/blob/master/making-metadata-changes.md) - [Commits](https://github.com/google/libphonenumber/compare/v8.12.20...v8.12.21) Signed-off-by: dependabot[bot] --- matrix-sdk-android/build.gradle | 2 +- vector/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 0ca605dc8b..2b3f727d9b 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -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/vector/build.gradle b/vector/build.gradle index bf164facee..89d8757803 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -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' From 49c79ceb74670d71bdcbcea6a2bd98c9bb13c4ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Apr 2021 07:22:36 +0000 Subject: [PATCH 187/249] Bump oss-licenses-plugin from 0.10.2 to 0.10.3 Bumps oss-licenses-plugin from 0.10.2 to 0.10.3. Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 2d4866cdc57508ed03f6e662ac40d4847b9b4b59 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 6 Apr 2021 09:56:12 +0200 Subject: [PATCH 188/249] Add better support for empty room name fallback --- CHANGES.md | 1 + .../common/TestRoomDisplayNameFallbackProvider.kt | 4 ++-- .../sdk/api/RoomDisplayNameFallbackProvider.kt | 2 +- .../room/membership/RoomDisplayNameResolver.kt | 13 +++++++++---- .../session/room/summary/RoomSummaryUpdater.kt | 2 +- .../session/sync/UserAccountDataSyncHandler.kt | 10 +++++++--- .../room/VectorRoomDisplayNameFallbackProvider.kt | 8 ++++++-- 7 files changed, 27 insertions(+), 13 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1601e0ec85..78c67092c4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,7 @@ 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) Bugfix 🐛: - Fix bad theme change for the MainActivity 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..c98bca42b9 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,12 +18,12 @@ 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?) = 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..b16d29c997 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,7 +18,7 @@ package org.matrix.android.sdk.api interface RoomDisplayNameFallbackProvider { fun getNameForRoomInvite(): String - fun getNameForEmptyRoom(): String + fun getNameForEmptyRoom(isDirect: Boolean, leftMemberNames: List): 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 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 f19862b397..2785ffe4cb 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 @@ -105,8 +106,12 @@ 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.queryRoomMembersEvent() + .findAll() + .filter { it.membership == Membership.LEAVE } + .map { it.getBestName() } + roomDisplayNameFallbackProvider.getNameForEmptyRoom(roomSummary?.isDirect.orFalse(), leftMembersNames) } 1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers) 2 -> { 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..02a9d4c1e6 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 @@ -102,7 +102,7 @@ internal class RoomSummaryUpdater @Inject constructor( // 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 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/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..fe1d4e0f2f 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,8 +28,12 @@ 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 { + context.getString(R.string.room_displayname_empty_room_was, leftMemberNames.joinToString()) + } } override fun getNameFor2members(name1: String?, name2: String?): String { From 7e74e6a6f4b2ae267fc85fe42467d6e05ca5d981 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 2 Apr 2021 18:03:58 +0200 Subject: [PATCH 189/249] Call getFixedRoomMemberContent() when loading room members of a room It fixes bad names for Empty room --- .../sdk/internal/session/events/EventExt.kt | 35 +++++++++++++++++++ .../room/membership/RoomMemberEventHandler.kt | 6 ++-- .../internal/session/sync/RoomSyncHandler.kt | 15 +------- 3 files changed, 39 insertions(+), 17 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/EventExt.kt 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/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/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt index e938d54903..3ea32b3bb4 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 @@ -464,18 +465,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 - } - } } From 15a463d748215d346c1426d20779ba8c4c9b1ca1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 2 Apr 2021 18:12:13 +0200 Subject: [PATCH 190/249] Small rework --- .../session/room/RoomAvatarResolver.kt | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) 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..0de8d6347a 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,29 @@ 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 + return 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 } } From 5dc28c0564e0f672e7517c0774143f67c29f2e95 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 2 Apr 2021 18:20:12 +0200 Subject: [PATCH 191/249] Fix avatar for DM when other user has left --- .../sdk/internal/session/room/RoomAvatarResolver.kt | 8 +++++++- .../session/room/membership/RoomDisplayNameResolver.kt | 3 +-- .../internal/session/room/membership/RoomMemberHelper.kt | 5 +++++ 3 files changed, 13 insertions(+), 3 deletions(-) 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 0de8d6347a..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 @@ -56,7 +56,13 @@ internal class RoomAvatarResolver @Inject constructor(@UserId private val userId if (isDirectRoom) { if (members.size == 1) { - return 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() return firstOtherMember?.avatarUrl 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 2785ffe4cb..194134f45d 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 @@ -107,9 +107,8 @@ internal class RoomDisplayNameResolver @Inject constructor( name = when (otherMembersCount) { 0 -> { // Get left members if any - val leftMembersNames = roomMembers.queryRoomMembersEvent() + val leftMembersNames = roomMembers.queryLeftRoomMembersEvent() .findAll() - .filter { it.membership == Membership.LEAVE } .map { it.getBestName() } roomDisplayNameFallbackProvider.getNameForEmptyRoom(roomSummary?.isDirect.orFalse(), leftMembersNames) } 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() From 33c1da5aa1c00495d3f901cce287439161be5bff Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 6 Apr 2021 14:15:05 +0200 Subject: [PATCH 192/249] Fix copyright --- .../sdk/api/session/room/UpdatableFilterLivePageResult.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt index 38462d5ac6..71b3c665e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 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. From e3cd91610524ab49e4592f66f70110156979c182 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 6 Apr 2021 16:25:16 +0200 Subject: [PATCH 193/249] Fix mandatory parameter in API (#3065) --- CHANGES.md | 1 + .../android/sdk/internal/session/room/RoomAPI.kt | 8 ++++++++ .../session/room/read/SetReadMarkersTask.kt | 13 ++++++++++--- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f9b05a32a7..a6f4c7e287 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ Bugfix 🐛: - Handle encrypted reactions (#2509) - Disable URL preview for some domains (#2995) - Fix avatar rendering for DMs, after initial sync (#2693) + - Fix mandatory parameter in API (#3065) Translations 🗣: - 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 ad699af6f4..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 @@ -153,6 +153,14 @@ internal interface RoomAPI { 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. * Ref: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-rooms-roomid-invite 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 35dc6a4f0c..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) { @@ -100,7 +100,14 @@ internal class DefaultSetReadMarkersTask @Inject constructor( globalErrorReceiver, canRetry = true ) { - roomAPI.sendReadMarker(params.roomId, markers) + 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) + } } } } @@ -110,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] From 62038e8a898d351064623bc7677bf275d05f66c1 Mon Sep 17 00:00:00 2001 From: Marcus Hoffmann Date: Tue, 6 Apr 2021 20:50:43 +0200 Subject: [PATCH 194/249] propagate jitsi LIBRE_BUILD exclusion rules to not pull in gms libs Previously jitsi only had optional dependencies being disabled by the LIBRE build flag but a while ago they introduced optional exclusion rules which don't transitively propagate to consumers of the library, so instead we need to mirror the exclusion rules from the jitsi gradle file: See: https://github.com/jitsi/jitsi-meet/blob/7a64bf006ea027b77564d8847570e1ac46ff0ec0/android/sdk/build.gradle#L53 Signed-off-by: Marcus Hoffmann --- vector/build.gradle | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index 3ee11450cc..d5a105d893 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -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 From 28897f76792301e242aa9050efaf9dc31f071589 Mon Sep 17 00:00:00 2001 From: Marcus Hoffmann Date: Tue, 6 Apr 2021 20:56:39 +0200 Subject: [PATCH 195/249] changes --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index f9b05a32a7..9a65297beb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -31,7 +31,7 @@ SDK API changes ⚠️: - Removes filtering options on Timeline. Build 🧱: - - + - Properly exclude gms dependencies in fdroid build flavour which were pulled in through the jitsi SDK (#3125) Test: - From 3a81521eabf75565a516f46b0ec0d9e2194c0213 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Apr 2021 10:55:43 +0200 Subject: [PATCH 196/249] Restore previous log when a request fails --- .../org/matrix/android/sdk/internal/network/Request.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 d9f102c7e0..e39bce6c67 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 @@ -55,9 +55,13 @@ internal suspend inline fun executeRequest(globalErrorReceiver: GlobalErr else -> throwable } - // Log some details about the request which has failed. This is less useful than before... - // Timber.e("Exception when executing request ${apiCall.request().method} ${apiCall.request().url.toString().substringBefore("?")}") - Timber.e("Exception when executing request") + // 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("?")}") + } // Check if this is a certificateException CertUtil.getCertificateException(exception) From c459c4f90cc45a3bd51ca35e23e15034e6de2207 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Apr 2021 10:59:41 +0200 Subject: [PATCH 197/249] Change snow chat effect message type from "nic.custom.snow" to "io.element.effect.snowfall" --- .../android/sdk/api/session/room/model/message/MessageType.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" } From 2495fa49f393f1dbeb7dbd3e4315f0af35900cb8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Apr 2021 12:39:36 +0200 Subject: [PATCH 198/249] Improve code clarity --- .../session/room/timeline/DefaultTimeline.kt | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) 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 -> From 511a0c22e245e1431dc0f2f5690538621be437a1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Apr 2021 13:45:43 +0200 Subject: [PATCH 199/249] Protect calls to FileService.downloadFile() --- .../home/room/detail/RoomDetailFragment.kt | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) 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) + } + ) } } From 884358b374702597cf2111ab5788479fd0b408c5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Apr 2021 13:47:20 +0200 Subject: [PATCH 200/249] Small cleanup --- .../roomprofile/uploads/RoomUploadsViewModel.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) 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) } } } From b8d01c4577ecaa30dd7ad82c05b89744dd06ea68 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Apr 2021 14:08:43 +0200 Subject: [PATCH 201/249] Fix quick click action (#3127) --- CHANGES.md | 1 + .../main/java/im/vector/app/core/utils/FirstThrottler.kt | 4 +++- .../features/settings/VectorSettingsHelpAboutFragment.kt | 7 ++++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4802f1bb12..c07d23fe9f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,6 +16,7 @@ Improvements 🙌: - 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) Bugfix 🐛: - Fix bad theme change for the MainActivity 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..3d52ca99db 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 @@ -23,7 +25,7 @@ class FirstThrottler(private val minimumInterval: Long = 800) { private var lastDate = 0L fun canHandle(): Boolean { - val now = System.currentTimeMillis() + val now = SystemClock.elapsedRealtime() if (now > lastDate + minimumInterval) { lastDate = now return true 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..17c5cad1c2 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()) { + activity?.displayInWebView(VectorSettingsUrls.THIRD_PARTY_LICENSES) + } false } From 1ac17b3aee69ed5f17e6e51aa51532a6072227e3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Apr 2021 14:34:06 +0200 Subject: [PATCH 202/249] Exclude instruction from try catch block --- .../im/vector/app/features/MainActivity.kt | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) 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..fd032c1d35 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() } } } From 835eb95aa15b9bc76e5614d370d70876ed3d20ee Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Apr 2021 14:52:27 +0200 Subject: [PATCH 203/249] If signout request fails, do not start LoginActivity, but restart the app (#3099) --- CHANGES.md | 1 + vector/src/main/java/im/vector/app/features/MainActivity.kt | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c07d23fe9f..13385123e6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,6 +24,7 @@ Bugfix 🐛: - Disable URL preview for some domains (#2995) - Fix avatar rendering for DMs, after initial sync (#2693) - Fix mandatory parameter in API (#3065) + - If signout request fails, do not start LoginActivity, but restart the app (#3099) Translations 🗣: - 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 fd032c1d35..34e73c8702 100644 --- a/vector/src/main/java/im/vector/app/features/MainActivity.kt +++ b/vector/src/main/java/im/vector/app/features/MainActivity.kt @@ -212,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) From 15c51cad1788ca3939d98f87dc9db9ff284c6188 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Apr 2021 15:17:02 +0200 Subject: [PATCH 204/249] Improve interface, name are not nullable --- .../sdk/common/TestRoomDisplayNameFallbackProvider.kt | 8 ++++---- .../android/sdk/api/RoomDisplayNameFallbackProvider.kt | 8 ++++---- .../session/room/membership/RoomDisplayNameResolver.kt | 5 ++--- .../room/VectorRoomDisplayNameFallbackProvider.kt | 8 ++++---- 4 files changed, 14 insertions(+), 15 deletions(-) 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 c98bca42b9..3f440a7d7b 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 @@ -26,15 +26,15 @@ class TestRoomDisplayNameFallbackProvider : RoomDisplayNameFallbackProvider { override fun getNameForEmptyRoom(isDirect: Boolean, leftMemberNames: List) = "Empty room" - override fun getNameFor2members(name1: String?, name2: String?) = + 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 b16d29c997..6028bc837d 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 @@ -19,8 +19,8 @@ package org.matrix.android.sdk.api interface RoomDisplayNameFallbackProvider { fun getNameForRoomInvite(): String fun getNameForEmptyRoom(isDirect: Boolean, leftMemberNames: List): 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 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/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt index 194134f45d..219ef694a4 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 @@ -149,9 +149,8 @@ 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.getBestName() 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 fe1d4e0f2f..57257dd597 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 @@ -36,19 +36,19 @@ class VectorRoomDisplayNameFallbackProvider( } } - override fun getNameFor2members(name1: String?, name2: String?): String { + 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, From 923715aeb313edfdd075640a593a87b4853e8584 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Apr 2021 15:19:33 +0200 Subject: [PATCH 205/249] Add method for name fallback when there is only one member --- .../sdk/common/TestRoomDisplayNameFallbackProvider.kt | 3 +++ .../android/sdk/api/RoomDisplayNameFallbackProvider.kt | 1 + .../session/room/membership/RoomDisplayNameResolver.kt | 6 +++++- .../features/room/VectorRoomDisplayNameFallbackProvider.kt | 2 ++ 4 files changed, 11 insertions(+), 1 deletion(-) 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 3f440a7d7b..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 @@ -26,6 +26,9 @@ class TestRoomDisplayNameFallbackProvider : RoomDisplayNameFallbackProvider { override fun getNameForEmptyRoom(isDirect: Boolean, leftMemberNames: List) = "Empty room" + override fun getNameFor1member(name: String) = + name + override fun getNameFor2members(name1: String, name2: String) = "$name1 and $name2" 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 6028bc837d..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 @@ -19,6 +19,7 @@ package org.matrix.android.sdk.api interface RoomDisplayNameFallbackProvider { fun getNameForRoomInvite(): 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 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 219ef694a4..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 @@ -112,7 +112,11 @@ internal class RoomDisplayNameResolver @Inject constructor( .map { it.getBestName() } roomDisplayNameFallbackProvider.getNameForEmptyRoom(roomSummary?.isDirect.orFalse(), leftMembersNames) } - 1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers) + 1 -> { + roomDisplayNameFallbackProvider.getNameFor1member( + resolveRoomMemberName(otherMembersSubset[0], roomMembers) + ) + } 2 -> { roomDisplayNameFallbackProvider.getNameFor2members( resolveRoomMemberName(otherMembersSubset[0], roomMembers), 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 57257dd597..a5f1ff58d6 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 @@ -36,6 +36,8 @@ class VectorRoomDisplayNameFallbackProvider( } } + 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) } From e811c53c44d9b108e0a062c3498b952677a79427 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Apr 2021 15:24:00 +0200 Subject: [PATCH 206/249] Improve "was" fallback for Empty rooms. --- .../room/VectorRoomDisplayNameFallbackProvider.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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 a5f1ff58d6..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 @@ -32,7 +32,14 @@ class VectorRoomDisplayNameFallbackProvider( return if (leftMemberNames.isEmpty()) { context.getString(R.string.room_displayname_empty_room) } else { - context.getString(R.string.room_displayname_empty_room_was, leftMemberNames.joinToString()) + 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) } } From af023669baded41cc3d21a5e76be832a04675008 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 24 Mar 2021 12:09:06 +0100 Subject: [PATCH 207/249] Implement FirstThrottler, to gain 200 ms for first refresh --- .../im/vector/app/core/extensions/LiveData.kt | 2 +- .../vector/app/core/utils/FirstThrottler.kt | 21 +++++++++++++++---- .../NotificationDrawerManager.kt | 10 +++++++-- .../VectorSettingsHelpAboutFragment.kt | 2 +- 4 files changed, 27 insertions(+), 8 deletions(-) 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/utils/FirstThrottler.kt b/vector/src/main/java/im/vector/app/core/utils/FirstThrottler.kt index 3d52ca99db..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 @@ -24,14 +24,27 @@ import android.os.SystemClock class FirstThrottler(private val minimumInterval: Long = 800) { private var lastDate = 0L - fun canHandle(): Boolean { + 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() - if (now > lastDate + minimumInterval) { + 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/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index 7f3c0a5beb..36b69a7958 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 @@ -194,10 +195,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 +211,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 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 17c5cad1c2..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 @@ -101,7 +101,7 @@ class VectorSettingsHelpAboutFragment @Inject constructor( // third party notice findPreference(VectorPreferences.SETTINGS_THIRD_PARTY_NOTICES_PREFERENCE_KEY)!! .onPreferenceClickListener = Preference.OnPreferenceClickListener { - if (firstThrottler.canHandle()) { + if (firstThrottler.canHandle() is FirstThrottler.CanHandlerResult.Yes) { activity?.displayInWebView(VectorSettingsUrls.THIRD_PARTY_LICENSES) } false From 96153fe92a3e865ecdb8164609ddd0400fcb36ef Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 24 Mar 2021 18:48:25 +0100 Subject: [PATCH 208/249] Get Event after a Push for a faster notification display in some conditions --- CHANGES.md | 1 + .../matrix/android/sdk/api/session/Session.kt | 2 + .../sdk/api/session/events/EventService.kt | 28 ++++++++++ .../internal/database/query/ReadQueries.kt | 23 ++++---- .../sdk/internal/session/DefaultSession.kt | 3 ++ .../sdk/internal/session/SessionModule.kt | 7 ++- .../session/call/CallEventProcessor.kt | 13 ++++- .../session/call/CallSignalingHandler.kt | 20 ++++--- .../session/events/DefaultEventService.kt | 40 ++++++++++++++ .../sdk/internal/session/room/RoomModule.kt | 5 ++ .../session/room/timeline/GetEventTask.kt | 43 +++++++++++---- .../fcm/VectorFirebaseMessagingService.kt | 54 ++++++++++++++++--- .../notifications/NotifiableEventResolver.kt | 30 +++++++++-- .../NotificationDrawerManager.kt | 6 ++- 14 files changed, 234 insertions(+), 41 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt diff --git a/CHANGES.md b/CHANGES.md index 13385123e6..86ba91c9df 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,7 @@ Improvements 🙌: - 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 Bugfix 🐛: - Fix bad theme change for the MainActivity 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..41bc0a1a62 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt @@ -0,0 +1,28 @@ +/* + * 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/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/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/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/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/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/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt index a9b8683a28..f1a20e8057 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 + val eventId: String, ) +} - override suspend fun execute(params: Params): Event { - return executeRequest(globalErrorReceiver) { +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/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..7f54be56fb 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 @@ -40,6 +40,9 @@ 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.pushrules.Action import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Event @@ -56,6 +59,8 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { private lateinit var activeSessionHolder: ActiveSessionHolder private lateinit var vectorPreferences: VectorPreferences + private val coroutineScope = CoroutineScope(SupervisorJob()) + // UI handler private val mUIHandler by lazy { Handler(Looper.getMainLooper()) @@ -78,6 +83,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 +100,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 +146,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 +164,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 +179,32 @@ 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 + } + + coroutineScope.launch { + Timber.d("Fast lane: start request") + val event = session.getEvent(roomId, eventId) + + val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event) + + // TODO Test the Event against the push rules + 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/java/im/vector/app/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt index a4f617bf5b..d5862f3a85 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 @@ -29,6 +29,7 @@ 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 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 +43,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 +86,28 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St } } + fun resolveInMemoryEvent(session: Session, event: Event): NotifiableEvent? { + if (event.getClearType() != EventType.MESSAGE) return null + + // TODO Ignore message edition + 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 + ) + ) + + return resolveMessageEvent(timelineEvent, session) + } + 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 36b69a7958..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 @@ -89,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 } @@ -550,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 } From 7f7f90f89e28340c7e02d153d98fbac35a802036 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 25 Mar 2021 14:11:22 +0100 Subject: [PATCH 209/249] ktlint --- .../android/sdk/internal/session/room/timeline/GetEventTask.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f1a20e8057..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 @@ -29,7 +29,7 @@ import javax.inject.Inject internal interface GetEventTask : Task { data class Params( val roomId: String, - val eventId: String, + val eventId: String ) } From 4a0a6e9f01d665505851fbb27221239c3223bc38 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 25 Mar 2021 14:42:15 +0100 Subject: [PATCH 210/249] FastLane: Ignore message edition --- .../org/matrix/android/sdk/api/session/events/model/Event.kt | 4 ++++ .../app/features/notifications/NotifiableEventResolver.kt | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) 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/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt b/vector/src/main/java/im/vector/app/features/notifications/NotifiableEventResolver.kt index d5862f3a85..477534eda5 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,6 +26,7 @@ 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 @@ -89,7 +90,9 @@ class NotifiableEventResolver @Inject constructor( fun resolveInMemoryEvent(session: Session, event: Event): NotifiableEvent? { if (event.getClearType() != EventType.MESSAGE) return null - // TODO Ignore message edition + // Ignore message edition + if (event.isEdition()) return null + val user = session.getUser(event.senderId!!) ?: return null val timelineEvent = TimelineEvent( From f0f66cbd0e687f7bdc219a2e1926ae4177fc3db6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 25 Mar 2021 14:57:32 +0100 Subject: [PATCH 211/249] Add comment --- .../matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt | 1 + 1 file changed, 1 insertion(+) 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 3ea32b3bb4..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 @@ -408,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, From 3a1b8bc33df89b6c89017091d0f27ba7629c6ed3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 25 Mar 2021 15:30:00 +0100 Subject: [PATCH 212/249] FastLane: handle push rules --- .../sdk/api/pushrules/PushRuleService.kt | 2 + .../notification/DefaultPushRuleService.kt | 20 +++++++++ .../notifications/NotifiableEventResolver.kt | 45 +++++++++++++------ 3 files changed, 53 insertions(+), 14 deletions(-) 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/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt index e00d2ff26c..8ee230192c 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,11 @@ 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.ConditionResolver 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 +48,7 @@ internal class DefaultPushRuleService @Inject constructor( private val addPushRuleTask: AddPushRuleTask, private val updatePushRuleActionsTask: UpdatePushRuleActionsTask, private val removePushRuleTask: RemovePushRuleTask, + private val conditionResolver: ConditionResolver, private val taskExecutor: TaskExecutor, @SessionDatabase private val monarchy: Monarchy ) : PushRuleService { @@ -130,6 +134,22 @@ internal class DefaultPushRuleService @Inject constructor( } } + override fun getActions(event: Event): List { + val rules = getPushRules(RuleScope.GLOBAL).getAllRules() + + return fulfilledBingRule(event, rules)?.getActions().orEmpty() + } + + // TODO This is a copy paste, try to have only once this code + 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 + } + } + // fun processEvents(events: List) { // var hasDoneSomething = false // events.forEach { event -> 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 477534eda5..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 @@ -93,22 +93,39 @@ class NotifiableEventResolver @Inject constructor( // Ignore message edition if (event.isEdition()) return null - val user = session.getUser(event.senderId!!) ?: return null + val actions = session.getActions(event) + val notificationAction = actions.toNotificationAction() - 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 - ) - ) + return if (notificationAction.shouldNotify) { + val user = session.getUser(event.senderId!!) ?: return null - return resolveMessageEvent(timelineEvent, session) + 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? { From c46f7fed5fc8374af2d2fc1e1c39977db91e7d69 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 25 Mar 2021 16:58:00 +0100 Subject: [PATCH 213/249] Avoid code duplication --- .../notification/DefaultPushRuleService.kt | 15 ++------ .../notification/ProcessEventForPushTask.kt | 15 ++------ .../session/notification/PushRuleFinder.kt | 35 +++++++++++++++++++ 3 files changed, 39 insertions(+), 26 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt 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 8ee230192c..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 @@ -17,7 +17,6 @@ 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.ConditionResolver import org.matrix.android.sdk.api.pushrules.PushRuleService import org.matrix.android.sdk.api.pushrules.RuleKind import org.matrix.android.sdk.api.pushrules.RuleScope @@ -48,7 +47,7 @@ internal class DefaultPushRuleService @Inject constructor( private val addPushRuleTask: AddPushRuleTask, private val updatePushRuleActionsTask: UpdatePushRuleActionsTask, private val removePushRuleTask: RemovePushRuleTask, - private val conditionResolver: ConditionResolver, + private val pushRuleFinder: PushRuleFinder, private val taskExecutor: TaskExecutor, @SessionDatabase private val monarchy: Monarchy ) : PushRuleService { @@ -137,17 +136,7 @@ internal class DefaultPushRuleService @Inject constructor( override fun getActions(event: Event): List { val rules = getPushRules(RuleScope.GLOBAL).getAllRules() - return fulfilledBingRule(event, rules)?.getActions().orEmpty() - } - - // TODO This is a copy paste, try to have only once this code - 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 - } + return pushRuleFinder.fulfilledBingRule(event, rules)?.getActions().orEmpty() } // fun processEvents(events: List) { 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 + } + } +} From 9a124f7630609e9c08358eb100c5c3c349009b07 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 25 Mar 2021 17:06:43 +0100 Subject: [PATCH 214/249] Done TODO --- .../vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt | 1 - 1 file changed, 1 deletion(-) 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 7f54be56fb..6bb28a9d77 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 @@ -194,7 +194,6 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event) - // TODO Test the Event against the push rules resolvedEvent ?.also { Timber.d("Fast lane: notify drawer") } ?.let { From b89a258fdf84938cd5e7f81139b30fd9e5221193 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 25 Mar 2021 18:30:54 +0100 Subject: [PATCH 215/249] FastLane: Only is Wifi is detected --- .../sdk/api/session/events/EventService.kt | 6 ++- .../sdk/internal/network/WifiDetector.kt | 45 +++++++++++++++++++ .../session/events/DefaultEventService.kt | 12 ++++- .../fcm/VectorFirebaseMessagingService.kt | 2 +- 4 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt 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 index 41bc0a1a62..3169c8107c 100644 --- 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 @@ -23,6 +23,10 @@ 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 + * @param onlyOnWifi if true and if WiFi is not available, no request will be done, + * and null will be returned */ - suspend fun getEvent(roomId: String, eventId: String): Event + suspend fun getEvent(roomId: String, + eventId: String, + onlyOnWifi: Boolean): Event? } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt new file mode 100644 index 0000000000..2159627a0b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/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 org.matrix.android.sdk.internal.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 + +internal 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/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 index d7e9ef2ee0..45b772f138 100644 --- 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 @@ -18,16 +18,24 @@ 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.network.WifiDetector import org.matrix.android.sdk.internal.session.call.CallEventProcessor import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask +import timber.log.Timber import javax.inject.Inject internal class DefaultEventService @Inject constructor( private val getEventTask: GetEventTask, - private val callEventProcessor: CallEventProcessor + private val callEventProcessor: CallEventProcessor, + private val wifiDetector: WifiDetector ) : EventService { - override suspend fun getEvent(roomId: String, eventId: String): Event { + override suspend fun getEvent(roomId: String, eventId: String, onlyOnWifi: Boolean): Event? { + if (onlyOnWifi && !wifiDetector.isConnectedToWifi()) { + Timber.d("No WiFi network, do not get Event") + return null + } + val event = getEventTask.execute(GetEventTask.Params(roomId, eventId)) // Fast lane to the call event processors: try to make the incoming call ring faster 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 6bb28a9d77..d421fc97e8 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 @@ -190,7 +190,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { coroutineScope.launch { Timber.d("Fast lane: start request") - val event = session.getEvent(roomId, eventId) + val event = session.getEvent(roomId, eventId, onlyOnWifi = true)?: return@launch val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event) From dead57b9feb78352b78b6988689f8b7e4490ebba Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Mar 2021 10:47:03 +0100 Subject: [PATCH 216/249] Cleanup --- .../org/matrix/android/sdk/internal/network/WifiDetector.kt | 2 +- .../vector/app/gplay/push/fcm/VectorFirebaseMessagingService.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt index 2159627a0b..b1ae4e3b94 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 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. 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 d421fc97e8..edde8e69b9 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 @@ -190,7 +190,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { coroutineScope.launch { Timber.d("Fast lane: start request") - val event = session.getEvent(roomId, eventId, onlyOnWifi = true)?: return@launch + val event = session.getEvent(roomId, eventId, onlyOnWifi = true) ?: return@launch val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event) From 7309c1066ce052de65d7d23f0b47d9b62de01051 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Apr 2021 15:07:22 +0200 Subject: [PATCH 217/249] Move WifiDetector to the app side And protect the call to getEvent() --- .../android/sdk/api/session/events/EventService.kt | 5 +---- .../internal/session/events/DefaultEventService.kt | 12 ++---------- .../gplay/push/fcm/VectorFirebaseMessagingService.kt | 11 ++++++++++- .../java/im/vector/app/core/di/VectorComponent.kt | 3 +++ .../java/im/vector/app/core}/network/WifiDetector.kt | 6 +++--- 5 files changed, 19 insertions(+), 18 deletions(-) rename {matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal => vector/src/main/java/im/vector/app/core}/network/WifiDetector.kt (90%) 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 index 3169c8107c..297f277497 100644 --- 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 @@ -23,10 +23,7 @@ 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 - * @param onlyOnWifi if true and if WiFi is not available, no request will be done, - * and null will be returned */ suspend fun getEvent(roomId: String, - eventId: String, - onlyOnWifi: Boolean): Event? + eventId: String): Event } 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 index 45b772f138..d7e9ef2ee0 100644 --- 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 @@ -18,24 +18,16 @@ 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.network.WifiDetector import org.matrix.android.sdk.internal.session.call.CallEventProcessor import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask -import timber.log.Timber import javax.inject.Inject internal class DefaultEventService @Inject constructor( private val getEventTask: GetEventTask, - private val callEventProcessor: CallEventProcessor, - private val wifiDetector: WifiDetector + private val callEventProcessor: CallEventProcessor ) : EventService { - override suspend fun getEvent(roomId: String, eventId: String, onlyOnWifi: Boolean): Event? { - if (onlyOnWifi && !wifiDetector.isConnectedToWifi()) { - Timber.d("No WiFi network, do not get Event") - return null - } - + 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 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 edde8e69b9..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 @@ -43,6 +44,7 @@ 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 @@ -58,6 +60,7 @@ 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()) @@ -74,6 +77,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { pusherManager = pusherManager() activeSessionHolder = activeSessionHolder() vectorPreferences = vectorPreferences() + wifiDetector = wifiDetector() } } @@ -188,9 +192,14 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() { 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 = session.getEvent(roomId, eventId, onlyOnWifi = true) ?: return@launch + val event = tryOrNull { session.getEvent(roomId, eventId) } ?: return@launch val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event) 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 cae7a2ece6..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 @@ -140,6 +141,8 @@ interface VectorComponent { fun vectorPreferences(): VectorPreferences + fun wifiDetector(): WifiDetector + fun vectorFileLogger(): VectorFileLogger fun uiStateRepository(): UiStateRepository diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt b/vector/src/main/java/im/vector/app/core/network/WifiDetector.kt similarity index 90% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt rename to vector/src/main/java/im/vector/app/core/network/WifiDetector.kt index b1ae4e3b94..34b2a7590d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/WifiDetector.kt +++ b/vector/src/main/java/im/vector/app/core/network/WifiDetector.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.network +package im.vector.app.core.network import android.content.Context import android.net.ConnectivityManager @@ -25,7 +25,7 @@ import org.matrix.android.sdk.api.extensions.orFalse import timber.log.Timber import javax.inject.Inject -internal class WifiDetector @Inject constructor( +class WifiDetector @Inject constructor( context: Context ) { private val connectivityManager = context.getSystemService()!! From 39c0c5401f59234db6433b2ce2dd5ab7cadd6125 Mon Sep 17 00:00:00 2001 From: Samu Voutilainen Date: Mon, 5 Apr 2021 07:13:08 +0000 Subject: [PATCH 218/249] Translated using Weblate (Finnish) Currently translated at 79.2% (1873 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fi/ --- vector/src/main/res/values-fi/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 From 3250f3bc0e1a09f3f102be3313b59cd4c8df2dba Mon Sep 17 00:00:00 2001 From: Magnus Date: Tue, 6 Apr 2021 00:52:31 +0000 Subject: [PATCH 219/249] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 62.9% (1486 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/nb_NO/ --- vector/src/main/res/values-nb-rNO/strings.xml | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/vector/src/main/res/values-nb-rNO/strings.xml b/vector/src/main/res/values-nb-rNO/strings.xml index fc16bd5ca0..f036ec7cd3 100644 --- a/vector/src/main/res/values-nb-rNO/strings.xml +++ b/vector/src/main/res/values-nb-rNO/strings.xml @@ -1027,7 +1027,7 @@ Start chat LAV PRIORITET FAVORITTER - DIREKTIV + KATALOG BLI MED Søking i krypterte rom støttes ikke ennå. FOLK @@ -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. From cc1d52171c2382cf7a4454c2fb1e9bb6e45179d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20L=C3=B8vbr=C3=B8tte=20Olsen?= Date: Mon, 5 Apr 2021 19:58:13 +0000 Subject: [PATCH 220/249] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegi?= =?UTF-8?q?an=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 62.9% (1486 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/nb_NO/ --- vector/src/main/res/values-nb-rNO/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vector/src/main/res/values-nb-rNO/strings.xml b/vector/src/main/res/values-nb-rNO/strings.xml index f036ec7cd3..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 @@ -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 From e9838f6db1c5ac3fe1f5e8e509367575a9161bcc Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Wed, 7 Apr 2021 10:06:20 +0000 Subject: [PATCH 221/249] Translated using Weblate (Russian) Currently translated at 99.4% (2350 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/ --- vector/src/main/res/values-ru/strings.xml | 68 +++++++++++++++++++++++ 1 file changed, 68 insertions(+) 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 From 3efd35e27a249e60d1025e336115b16df0962bf6 Mon Sep 17 00:00:00 2001 From: ozzii Date: Tue, 6 Apr 2021 11:36:14 +0000 Subject: [PATCH 222/249] Translated using Weblate (Serbian) Currently translated at 28.1% (666 of 2362 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sr/ --- vector/src/main/res/values-sr/strings.xml | 47 +++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/vector/src/main/res/values-sr/strings.xml b/vector/src/main/res/values-sr/strings.xml index 5e56ffcad5..065fd3154b 100644 --- a/vector/src/main/res/values-sr/strings.xml +++ b/vector/src/main/res/values-sr/strings.xml @@ -711,4 +711,51 @@ Придружи се Избриши Настави + Послати одговор (НЕшифрован)… + Послати шифрован дговор… + Послати поруку (НЕшифровану)… + Послати шифровану поруку… + %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 From 5a988d6f4af52aeeb091f3dc11ed794a32562354 Mon Sep 17 00:00:00 2001 From: Samu Voutilainen Date: Mon, 5 Apr 2021 07:08:26 +0000 Subject: [PATCH 223/249] Translated using Weblate (Finnish) Currently translated at 100.0% (13 of 13 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fi/ --- fastlane/metadata/android/fi/changelogs/40101000.txt | 2 ++ fastlane/metadata/android/fi/changelogs/40101010.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/fi/changelogs/40101000.txt create mode 100644 fastlane/metadata/android/fi/changelogs/40101010.txt 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 From 57d94c549465b174273a9b3fa24ff52c8b43fb02 Mon Sep 17 00:00:00 2001 From: Nikita Epifanov Date: Wed, 7 Apr 2021 10:08:50 +0000 Subject: [PATCH 224/249] Translated using Weblate (Russian) Currently translated at 100.0% (13 of 13 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/ru/ --- fastlane/metadata/android/ru/changelogs/40101000.txt | 2 ++ fastlane/metadata/android/ru/changelogs/40101010.txt | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 fastlane/metadata/android/ru/changelogs/40101000.txt create mode 100644 fastlane/metadata/android/ru/changelogs/40101010.txt 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 From 5971edee147b5deae2ce4cdf46f49a81501dd21f Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Tue, 6 Apr 2021 12:21:58 +0000 Subject: [PATCH 225/249] Translated using Weblate (Persian) Currently translated at 100.0% (13 of 13 strings) Translation: Element Android/Element Android Store Translate-URL: https://translate.element.io/projects/element-android/element-store/fa/ --- fastlane/metadata/android/fa/changelogs/40100120.txt | 2 ++ fastlane/metadata/android/fa/changelogs/40100130.txt | 2 ++ fastlane/metadata/android/fa/changelogs/40100140.txt | 2 ++ fastlane/metadata/android/fa/changelogs/40100150.txt | 2 ++ fastlane/metadata/android/fa/changelogs/40100160.txt | 2 ++ fastlane/metadata/android/fa/changelogs/40100170.txt | 2 ++ fastlane/metadata/android/fa/changelogs/40101000.txt | 2 ++ fastlane/metadata/android/fa/changelogs/40101010.txt | 2 ++ 8 files changed, 16 insertions(+) create mode 100644 fastlane/metadata/android/fa/changelogs/40100120.txt create mode 100644 fastlane/metadata/android/fa/changelogs/40100130.txt create mode 100644 fastlane/metadata/android/fa/changelogs/40100140.txt create mode 100644 fastlane/metadata/android/fa/changelogs/40100150.txt create mode 100644 fastlane/metadata/android/fa/changelogs/40100160.txt create mode 100644 fastlane/metadata/android/fa/changelogs/40100170.txt create mode 100644 fastlane/metadata/android/fa/changelogs/40101000.txt create mode 100644 fastlane/metadata/android/fa/changelogs/40101010.txt 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 From fc202437e85535185056736361d65168e58a604f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Apr 2021 18:31:46 +0200 Subject: [PATCH 226/249] Fix lint issues --- vector/src/main/res/values-ar/strings.xml | 1 - vector/src/main/res/values-tr/strings.xml | 11 ++--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/vector/src/main/res/values-ar/strings.xml b/vector/src/main/res/values-ar/strings.xml index bc0738643f..4e74426e9a 100644 --- a/vector/src/main/res/values-ar/strings.xml +++ b/vector/src/main/res/values-ar/strings.xml @@ -11,7 +11,6 @@ رفعَ ⁨%1$s⁩ المنع عن ⁨%2$s⁩ منعَ ⁨%1$s⁩ ⁨%2$s⁩ غيّر ⁨%1$s⁩ صورته الشخصية - ضبطَ ⁨%1$s⁩ اسم العرض على إنَّ %1$s قد غيَّرَ اسمه الظاهر من %2$s إلى %3$s إنَّ %1$s قد أزالَ اسمه الظاهر (لقد كان %2$s) إنَّ %1$s قد غيَّرَ الموضوع إلى: %2$s diff --git a/vector/src/main/res/values-tr/strings.xml b/vector/src/main/res/values-tr/strings.xml index aa234ac78c..4739830ea4 100644 --- a/vector/src/main/res/values-tr/strings.xml +++ b/vector/src/main/res/values-tr/strings.xml @@ -1761,8 +1761,6 @@ %s aramayı sonlandırdı. Aramayı cevapladınız. %s aramayı cevapladı. - " -\n" Bir sesli arama başlattınız. %s bir sesli arama başlattı. Görüntülü arama başlattınız. @@ -1784,15 +1782,11 @@ Oda sürümü Mesaj gönderiliyor… Boş oda - - - - - %1$s, %2$s ve + %1$s, %2$s ve %3$s Özel %1$s widgetını kaldırdınız %1$s widgetını eklediniz - Profilinizi güncellediniz + Profilinizi güncellediniz %1$s Oda daveti Telefon numarası E-posta adresi @@ -1801,7 +1795,6 @@ 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 From 47aeadef7c0313ec315b19b976f0597083d2b354 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Apr 2021 18:34:54 +0200 Subject: [PATCH 227/249] Add configuration for new Irish language --- vector/src/main/res/values-ga/strings_no_weblate.xml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 vector/src/main/res/values-ga/strings_no_weblate.xml 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 From e7608469ca16269ac45d5dcd8ad2aadd033fe7b5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Apr 2021 18:42:52 +0200 Subject: [PATCH 228/249] Import SAS strings --- .../src/main/res/values-fi/strings_sas.xml | 2 +- .../src/main/res/values-ja/strings_sas.xml | 48 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) 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 @@ + ライオン + ユニコーン + ブタ + ゾウ + うさぎ + パンダ + ニワトリ + ペンギン + + たこ + ちょうちょ + サボテン きのこ + 地球 + + + バナナ リンゴ + いちご + とうもろこし + ピザ ケーキ + ハート + スマイル ロボと + 帽子 めがね + スパナ + サンタ + いいね + + 砂時計 + 時計 + ギフト + 電球 + 鉛筆 + クリップ + はさみ + 錠前 + + 金槌 電話機 + 電車 自転車 + 飛行機 + ロケット + トロフィー + ボール + ギター + トランペット + ベル + いかり + ヘッドホン + フォルダ + ピン From b423d5da4c9f24bc46abe55e810ec496e30bb14b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 7 Apr 2021 19:00:02 +0200 Subject: [PATCH 229/249] typo --- tools/import_emojis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/import_emojis.py b/tools/import_emojis.py index f5638175a9..a626a5a260 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') From 9b5bc60fa9674523c2de1eadd31ab5d17f8b57ab Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 8 Apr 2021 09:54:51 +0200 Subject: [PATCH 230/249] Remove unused parameter and use same value than the JS SDK --- .../org/matrix/android/sdk/internal/network/Request.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) 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 e39bce6c67..170ce09149 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 @@ -28,22 +28,21 @@ import java.io.IOException /** * 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 initialDelayBeforeRetry the first delay to wait before a request is retried. Will be doubled after each retry * @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, - initialDelayBeforeRetry: Long = 100L, - maxDelayBeforeRetry: Long = 10_000L, - maxRetriesCount: Int = Int.MAX_VALUE, + maxDelayBeforeRetry: Long = 32_000L, + maxRetriesCount: Int = 4, noinline requestBlock: suspend () -> DATA): DATA { var currentRetryCount = 0 - var currentDelay = initialDelayBeforeRetry + var currentDelay = 1_000L while (true) { try { From 8dead986a5c5d6d7febb5729e68623f4a9dbc764 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 8 Apr 2021 10:59:51 +0200 Subject: [PATCH 231/249] Always try to retry Http requests in case of 429 (#1300) --- CHANGES.md | 1 + .../matrix/android/sdk/internal/network/Request.kt | 14 +++++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 86ba91c9df..1a684ee56f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -18,6 +18,7 @@ Improvements 🙌: - 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) Bugfix 🐛: - Fix bad theme change for the MainActivity 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 170ce09149..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,6 +19,7 @@ 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 @@ -71,9 +72,16 @@ internal suspend inline fun executeRequest(globalErrorReceiver: GlobalErr // } ?.also { unrecognizedCertificateException -> throw unrecognizedCertificateException } - if (canRetry && currentRetryCount++ < maxRetriesCount && exception.shouldBeRetried()) { - // In case of 429, ensure we wait enough - delay(currentDelay.coerceAtLeast(exception.getRetryDelay(0))) + 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.times(2L).coerceAtMost(maxDelayBeforeRetry) // Try again (loop) } else { From 760e14531f31bc4bd6a61ae931c94eddc750bf6e Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 8 Apr 2021 12:09:35 +0200 Subject: [PATCH 232/249] Fix EW-EA compatibility for verification --- .../verification/IncomingVerificationRequestHandler.kt | 10 +++++++++- .../vector/app/features/home/HomeActivityViewModel.kt | 5 ++--- 2 files changed, 11 insertions(+), 4 deletions(-) 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..45feaa47c3 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,14 @@ 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..523898e0f5 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 ) ) } From c6bd37810457c93a6605a2d65d86579a227dd4bf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 8 Apr 2021 12:44:28 +0200 Subject: [PATCH 233/249] Test is passing --- .../androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt | 1 + 1 file changed, 1 insertion(+) 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 From 7b1d313e8ee2e56406f8285aaf8ec4a766ff7788 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 8 Apr 2021 12:46:55 +0200 Subject: [PATCH 234/249] Small cleanup --- .../android/sdk/internal/session/room/send/SendEventWorker.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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() From 1715143b85175993625acac274eda2cd6cb50b46 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 8 Apr 2021 16:18:58 +0200 Subject: [PATCH 235/249] Filter some other words Cannot filter canonical alias anymore, as we now use word boundaries --- .../roomdirectory/ExplicitTermFilterTest.kt | 118 ++++++++++++++++++ vector/src/main/assets/forbidden_terms.txt | 71 +++++++++++ .../roomdirectory/ExplicitTermFilter.kt | 39 ++++++ .../roomdirectory/RoomDirectoryViewModel.kt | 15 +-- 4 files changed, 231 insertions(+), 12 deletions(-) create mode 100644 vector/src/androidTest/java/im/vector/app/features/roomdirectory/ExplicitTermFilterTest.kt create mode 100644 vector/src/main/assets/forbidden_terms.txt create mode 100644 vector/src/main/java/im/vector/app/features/roomdirectory/ExplicitTermFilter.kt 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..a5d8108ae9 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/features/roomdirectory/ExplicitTermFilterTest.kt @@ -0,0 +1,118 @@ +/* + * 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 + } +} \ No newline at end of file diff --git a/vector/src/main/assets/forbidden_terms.txt b/vector/src/main/assets/forbidden_terms.txt new file mode 100644 index 0000000000..693da9c520 --- /dev/null +++ b/vector/src/main/assets/forbidden_terms.txt @@ -0,0 +1,71 @@ +anal +bbc +bbw +bdsm +beast +bestiality +blowjob +bondage +boobs +clit +cock +cuck +cum +cunt +daddy +dick +dildo +erotic +exhibitionism +faggot +feet +femboy +fisting +flogging +fmf +foursome +futa +gangbang +gore +h3ntai +handjob +hentai +incest +jizz +kink +loli +m4f +masturbation +mfm +mfm +milf +moresome +naked +neet +nipple +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 \ No newline at end of file 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..addc04ed96 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/ExplicitTermFilter.kt @@ -0,0 +1,39 @@ +/* + * 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 explicitContentRegex = assetReader.readAssetFile("forbidden_terms.txt") + .orEmpty() + .split("\n") + .map { it.trim() } + .filter { it.isNotEmpty() } + .joinToString(prefix = ".*\\b(", separator = "|", postfix = ")\\b.*") + .toRegex(RegexOption.IGNORE_CASE) + + 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 4ef38758c7..9932fdb551 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 @@ -42,12 +42,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 @@ -58,11 +58,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() @@ -202,11 +197,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 { From 153d393bf1a9a33316bd7fd7a22da653f6279a87 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 8 Apr 2021 17:28:47 +0200 Subject: [PATCH 236/249] Prevent searching for forbidden terms --- .../features/roomdirectory/ExplicitTermFilterTest.kt | 10 ++++++++++ .../app/features/roomdirectory/ExplicitTermFilter.kt | 8 +++++++- .../features/roomdirectory/RoomDirectoryViewModel.kt | 11 +++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) 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 index a5d8108ae9..7c66ad7462 100644 --- a/vector/src/androidTest/java/im/vector/app/features/roomdirectory/ExplicitTermFilterTest.kt +++ b/vector/src/androidTest/java/im/vector/app/features/roomdirectory/ExplicitTermFilterTest.kt @@ -115,4 +115,14 @@ class ExplicitTermFilterTest : InstrumentedTest { fun isValidLastFalse() { explicitTermFilter.isValid("zoo") shouldBe false } + + @Test + fun canSearchForFalse() { + explicitTermFilter.canSearchFor("zoo") shouldBe false + } + + @Test + fun canSearchForTrue() { + explicitTermFilter.canSearchFor("android") shouldBe true + } } \ No newline at end of file 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 index addc04ed96..8abccbbe5e 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/ExplicitTermFilter.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/ExplicitTermFilter.kt @@ -23,14 +23,20 @@ class ExplicitTermFilter @Inject constructor( assetReader: AssetReader ) { // List of forbidden terms is in file asset forbidden_terms.txt, in lower case - private val explicitContentRegex = assetReader.readAssetFile("forbidden_terms.txt") + private val explicitTerms = assetReader.readAssetFile("forbidden_terms.txt") .orEmpty() .split("\n") .map { it.trim() } .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 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 9932fdb551..a6c4646f8c 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 @@ -161,6 +161,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, From 2e9f8ae6ae728a1d2a279d455cf2fdabf154ef8a Mon Sep 17 00:00:00 2001 From: Aleks Date: Mon, 29 Mar 2021 09:20:22 +0200 Subject: [PATCH 237/249] registration availability endpoint added --- .../android/sdk/api/auth/data/Availability.kt | 26 ++++++++++++++++ .../registration/RegistrationAvailability.kt | 24 +++++++++++++++ .../auth/registration/RegistrationWizard.kt | 2 ++ .../android/sdk/api/failure/Extensions.kt | 8 +++++ .../android/sdk/internal/auth/AuthAPI.kt | 15 ++++++---- .../registration/DefaultRegistrationWizard.kt | 21 ++++++++++--- .../registration/RegisterAvailableTask.kt | 30 +++++++++++++++++++ 7 files changed, 116 insertions(+), 10 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/Availability.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationAvailability.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAvailableTask.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/Availability.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/Availability.kt new file mode 100644 index 0000000000..3fbbdd161f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/Availability.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2020 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.data + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class Availability( + @Json(name = "available") + val available: Boolean +) 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..7c7d769a35 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationAvailability.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2020 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 { + data class Available(val available: Boolean): RegistrationAvailability() + data class NotAvailable(val failure: Failure.ServerError): RegistrationAvailability() +} \ No newline at end of file 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 17362ff8d7..7f25eedb23 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 @@ -87,3 +87,11 @@ fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? { null } } + +fun Throwable.isRegistrationAvailabilityError(): Boolean { + return this is Failure.ServerError + && (error.code == MatrixError.M_USER_IN_USE + || error.code == MatrixError.M_INVALID_USERNAME + || error.code == MatrixError.M_EXCLUSIVE) + && httpCode == 400 +} 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 2ce5c67a94..6566aae5fe 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 @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.auth +import org.matrix.android.sdk.api.auth.data.Availability import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams @@ -29,12 +30,8 @@ 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.http.Body -import retrofit2.http.GET -import retrofit2.http.Headers -import retrofit2.http.POST -import retrofit2.http.Path -import retrofit2.http.Url +import retrofit2.Call +import retrofit2.http.* /** * The login REST API. @@ -65,6 +62,12 @@ internal interface AuthAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register") 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") + fun registerAvailable(@Query("username") username: String): Call + /** * Add 3Pid during registration * Ref: https://gist.github.com/jryans/839a09bf0c5a70e2f36ed990d50ed928 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..3be95f6a4a 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 @@ -18,12 +18,10 @@ 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.RegistrationResult -import org.matrix.android.sdk.api.auth.registration.RegistrationWizard -import org.matrix.android.sdk.api.auth.registration.toFlowResult +import org.matrix.android.sdk.api.auth.registration.* import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure.RegistrationFlowError +import org.matrix.android.sdk.api.failure.isRegistrationAvailabilityError import org.matrix.android.sdk.internal.auth.AuthAPI import org.matrix.android.sdk.internal.auth.PendingSessionStore import org.matrix.android.sdk.internal.auth.SessionCreator @@ -41,6 +39,7 @@ internal class DefaultRegistrationWizard( private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here") private val registerTask = DefaultRegisterTask(authAPI) + private val registerAvailableTask = RegisterAvailableTask(authAPI) private val registerAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI) private val validateCodeTask = DefaultValidateCodeTask(authAPI) @@ -203,4 +202,18 @@ internal class DefaultRegistrationWizard( val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) return RegistrationResult.Success(session) } + + override suspend fun registrationAvailable(userName: String): RegistrationAvailability { + val availability = try { + registerAvailableTask.execute(userName) + } catch (exception: Throwable) { + if(exception.isRegistrationAvailabilityError()) { + return RegistrationAvailability.NotAvailable(exception as Failure.ServerError) + } else { + throw exception + } + } + + return RegistrationAvailability.Available(availability.available) + } } 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..aa2bb767f9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAvailableTask.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2020 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.data.Availability +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 class RegisterAvailableTask(private val authAPI: AuthAPI) : Task { + override suspend fun execute(params: String): Availability { + return executeRequest(null) { + apiCall = authAPI.registerAvailable(params) + } + } +} \ No newline at end of file From 9ce9d1e5491b800bc3291bd575f45ad5742959cb Mon Sep 17 00:00:00 2001 From: Aleks Date: Mon, 29 Mar 2021 15:31:53 +0200 Subject: [PATCH 238/249] CHANGES.md updated --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 1a684ee56f..3362588cf8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,6 +19,7 @@ Improvements 🙌: - 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 From 4451b682b17ba9d4cb6a853a747fad94e93f5d6a Mon Sep 17 00:00:00 2001 From: Aleks Date: Thu, 8 Apr 2021 11:30:41 +0200 Subject: [PATCH 239/249] merge + register/available endpoint corrections --- .../android/sdk/api/auth/data/Availability.kt | 2 +- .../registration/RegistrationAvailability.kt | 4 ++-- .../android/sdk/internal/auth/AuthAPI.kt | 2 +- .../registration/DefaultRegistrationWizard.kt | 15 +----------- .../registration/RegisterAvailableTask.kt | 24 ++++++++++++++----- 5 files changed, 23 insertions(+), 24 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/Availability.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/Availability.kt index 3fbbdd161f..93e0ac4e44 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/Availability.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/Availability.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) -data class Availability( +internal data class Availability( @Json(name = "available") val available: Boolean ) 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 index 7c7d769a35..5f944a86d9 100644 --- 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 @@ -19,6 +19,6 @@ package org.matrix.android.sdk.api.auth.registration import org.matrix.android.sdk.api.failure.Failure sealed class RegistrationAvailability { - data class Available(val available: Boolean): RegistrationAvailability() + object Available : RegistrationAvailability() data class NotAvailable(val failure: Failure.ServerError): RegistrationAvailability() -} \ No newline at end of file +} 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 6566aae5fe..216be83ea3 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 @@ -66,7 +66,7 @@ internal interface AuthAPI { * Checks to see if a username is available, and valid, for the server. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register/available") - fun registerAvailable(@Query("username") username: String): Call + suspend fun registerAvailable(@Query("username") username: String): Availability /** * Add 3Pid during registration 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 3be95f6a4a..ace2e62043 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 @@ -21,7 +21,6 @@ import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.* import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure.RegistrationFlowError -import org.matrix.android.sdk.api.failure.isRegistrationAvailabilityError import org.matrix.android.sdk.internal.auth.AuthAPI import org.matrix.android.sdk.internal.auth.PendingSessionStore import org.matrix.android.sdk.internal.auth.SessionCreator @@ -203,17 +202,5 @@ internal class DefaultRegistrationWizard( return RegistrationResult.Success(session) } - override suspend fun registrationAvailable(userName: String): RegistrationAvailability { - val availability = try { - registerAvailableTask.execute(userName) - } catch (exception: Throwable) { - if(exception.isRegistrationAvailabilityError()) { - return RegistrationAvailability.NotAvailable(exception as Failure.ServerError) - } else { - throw exception - } - } - - return RegistrationAvailability.Available(availability.available) - } + override suspend fun registrationAvailable(userName: String): RegistrationAvailability = registerAvailableTask.execute(userName) } 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 index aa2bb767f9..791b725357 100644 --- 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 @@ -16,15 +16,27 @@ package org.matrix.android.sdk.internal.auth.registration -import org.matrix.android.sdk.api.auth.data.Availability +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 class RegisterAvailableTask(private val authAPI: AuthAPI) : Task { - override suspend fun execute(params: String): Availability { - return executeRequest(null) { - apiCall = authAPI.registerAvailable(params) +internal class RegisterAvailableTask(private val authAPI: AuthAPI) : Task { + override suspend fun execute(params: String): RegistrationAvailability { + try { + executeRequest(null) { + authAPI.registerAvailable(params) + } + } catch (exception: Throwable) { + if(exception.isRegistrationAvailabilityError()) { + return RegistrationAvailability.NotAvailable(exception as Failure.ServerError) + } else { + throw exception + } } + + return RegistrationAvailability.Available } -} \ No newline at end of file +} From 9d96f90e49dcd05319b06ae6457989add0578b0e Mon Sep 17 00:00:00 2001 From: Aleks Date: Thu, 8 Apr 2021 12:15:19 +0200 Subject: [PATCH 240/249] keyword-spacing fix + wildcard-imports fix --- .../internal/auth/registration/DefaultRegistrationWizard.kt | 6 +++++- .../sdk/internal/auth/registration/RegisterAvailableTask.kt | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) 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 ace2e62043..16c05dd8a9 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 @@ -18,7 +18,11 @@ 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.* +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 import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure.RegistrationFlowError import org.matrix.android.sdk.internal.auth.AuthAPI 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 index 791b725357..d1aa265636 100644 --- 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 @@ -30,7 +30,7 @@ internal class RegisterAvailableTask(private val authAPI: AuthAPI) : Task Date: Thu, 8 Apr 2021 18:48:43 +0200 Subject: [PATCH 241/249] Some cleanup after merge of #3084 --- .../registration/RegistrationAvailability.kt | 4 ++-- .../android/sdk/api/failure/Extensions.kt | 13 +++++++----- .../android/sdk/internal/auth/AuthAPI.kt | 2 +- .../auth/data/Availability.kt | 11 ++++++---- .../registration/DefaultRegistrationWizard.kt | 12 ++++++----- .../registration/RegisterAvailableTask.kt | 21 ++++++++++++------- 6 files changed, 38 insertions(+), 25 deletions(-) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{api => internal}/auth/data/Availability.kt (68%) 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 index 5f944a86d9..f9a7ace7ba 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * 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. @@ -20,5 +20,5 @@ import org.matrix.android.sdk.api.failure.Failure sealed class RegistrationAvailability { object Available : RegistrationAvailability() - data class NotAvailable(val failure: Failure.ServerError): RegistrationAvailability() + data class NotAvailable(val failure: Failure.ServerError) : RegistrationAvailability() } 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 7f25eedb23..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 @@ -65,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( @@ -90,8 +93,8 @@ fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? { 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) - && httpCode == 400 + || error.code == MatrixError.M_INVALID_USERNAME + || error.code == MatrixError.M_EXCLUSIVE) } 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 216be83ea3..d6a197b06b 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 @@ -16,8 +16,8 @@ package org.matrix.android.sdk.internal.auth -import org.matrix.android.sdk.api.auth.data.Availability 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 diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/Availability.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/Availability.kt similarity index 68% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/Availability.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/Availability.kt index 93e0ac4e44..5ef3c0d06a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/Availability.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/Availability.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * 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. @@ -14,13 +14,16 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.auth.data +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( - @Json(name = "available") - val available: Boolean + /** + * 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/registration/DefaultRegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt index 16c05dd8a9..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 @@ -41,10 +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 registerAvailableTask = RegisterAvailableTask(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() { @@ -206,5 +206,7 @@ internal class DefaultRegistrationWizard( return RegistrationResult.Success(session) } - override suspend fun registrationAvailable(userName: String): RegistrationAvailability = registerAvailableTask.execute(userName) + 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/RegisterAvailableTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAvailableTask.kt index d1aa265636..314a24dad4 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * 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. @@ -23,20 +23,25 @@ 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 class RegisterAvailableTask(private val authAPI: AuthAPI) : Task { - override suspend fun execute(params: String): RegistrationAvailability { - try { +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) + authAPI.registerAvailable(params.userName) } + RegistrationAvailability.Available } catch (exception: Throwable) { if (exception.isRegistrationAvailabilityError()) { - return RegistrationAvailability.NotAvailable(exception as Failure.ServerError) + RegistrationAvailability.NotAvailable(exception as Failure.ServerError) } else { throw exception } } - - return RegistrationAvailability.Available } } From ee3eb8e1d6744049e524f8e55342506437cc019d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 8 Apr 2021 18:52:53 +0200 Subject: [PATCH 242/249] Cleanup --- .../java/org/matrix/android/sdk/internal/auth/AuthAPI.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 d6a197b06b..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 @@ -30,8 +30,13 @@ 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.* +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 /** * The login REST API. From 1233fde2617ceaf40dc52e79514d2c0660e6c83f Mon Sep 17 00:00:00 2001 From: Matthew Hodgson Date: Thu, 8 Apr 2021 18:26:55 +0100 Subject: [PATCH 243/249] Update forbidden_terms.txt --- vector/src/main/assets/forbidden_terms.txt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/vector/src/main/assets/forbidden_terms.txt b/vector/src/main/assets/forbidden_terms.txt index 693da9c520..84e7fe1d28 100644 --- a/vector/src/main/assets/forbidden_terms.txt +++ b/vector/src/main/assets/forbidden_terms.txt @@ -1,5 +1,4 @@ anal -bbc bbw bdsm beast @@ -18,7 +17,6 @@ dildo erotic exhibitionism faggot -feet femboy fisting flogging @@ -35,14 +33,13 @@ jizz kink loli m4f +masturbate masturbation mfm -mfm milf moresome naked neet -nipple nsfw nude nudity @@ -68,4 +65,4 @@ vibrator voyeur watersports xxx -zoo \ No newline at end of file +zoo From f1e280827de7043d3c1a5a77d001bbf0e545b9f0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 8 Apr 2021 19:39:49 +0200 Subject: [PATCH 244/249] Ensure there is no dup and cleanup --- .../vector/app/features/roomdirectory/ExplicitTermFilterTest.kt | 2 +- .../im/vector/app/features/roomdirectory/ExplicitTermFilter.kt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) 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 index 7c66ad7462..b2beec5b66 100644 --- a/vector/src/androidTest/java/im/vector/app/features/roomdirectory/ExplicitTermFilterTest.kt +++ b/vector/src/androidTest/java/im/vector/app/features/roomdirectory/ExplicitTermFilterTest.kt @@ -125,4 +125,4 @@ class ExplicitTermFilterTest : InstrumentedTest { fun canSearchForTrue() { explicitTermFilter.canSearchFor("android") shouldBe true } -} \ No newline at end of file +} 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 index 8abccbbe5e..0d1f55485c 100644 --- a/vector/src/main/java/im/vector/app/features/roomdirectory/ExplicitTermFilter.kt +++ b/vector/src/main/java/im/vector/app/features/roomdirectory/ExplicitTermFilter.kt @@ -27,6 +27,7 @@ class ExplicitTermFilter @Inject constructor( .orEmpty() .split("\n") .map { it.trim() } + .distinct() .filter { it.isNotEmpty() } private val explicitContentRegex = explicitTerms From a4a37777229fd4fe6afd6e5beeba4f9342850699 Mon Sep 17 00:00:00 2001 From: oogm Date: Thu, 8 Apr 2021 22:14:41 +0200 Subject: [PATCH 245/249] Update import_emojis.py to retain keyword order --- tools/import_emojis.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tools/import_emojis.py b/tools/import_emojis.py index a626a5a260..30db3b0b13 100755 --- a/tools/import_emojis.py +++ b/tools/import_emojis.py @@ -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'] From 327e75e8e59cdcbbf628ff0e262def8606b6cf7c Mon Sep 17 00:00:00 2001 From: oogm Date: Thu, 8 Apr 2021 22:26:31 +0200 Subject: [PATCH 246/249] Update CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 3362588cf8..e419cccba7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -28,6 +28,7 @@ Bugfix 🐛: - Fix avatar rendering for DMs, after initial sync (#2693) - 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 (#3147) Translations 🗣: - From 5311bacfff8eb5464a8032e8967f883b067f1a57 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Apr 2021 10:26:28 +0200 Subject: [PATCH 247/249] ktlint --- .../crypto/verification/IncomingVerificationRequestHandler.kt | 1 - .../java/im/vector/app/features/home/HomeActivityViewModel.kt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) 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 45feaa47c3..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 @@ -119,7 +119,6 @@ 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 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 523898e0f5..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,7 +203,7 @@ class HomeActivityViewModel @AssistedInject constructor( _viewEvents.post( HomeActivityViewEvents.OnNewSession( session.getUser(session.myUserId)?.toMatrixItem(), - //Always send request instead of waiting for an incoming as per recent EW changes + // Always send request instead of waiting for an incoming as per recent EW changes false ) ) From 93bc5abcb58f47b7574c7395cbd0bebf1b24d0cd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Apr 2021 10:32:11 +0200 Subject: [PATCH 248/249] Update the Emoji source file --- CHANGES.md | 2 +- vector/src/main/res/raw/emoji_picker_datasource.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e419cccba7..996e59a00c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -28,7 +28,7 @@ Bugfix 🐛: - Fix avatar rendering for DMs, after initial sync (#2693) - 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 (#3147) + - Retain keyword order in emoji import script, and update the generated file (#3147) Translations 🗣: - 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 From 5ee8cf7ebf6cbda0bdd292fd264f8dfe62e61027 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 9 Apr 2021 12:38:55 +0200 Subject: [PATCH 249/249] Prepare release 1.1.4 --- CHANGES.md | 11 +---------- .../metadata/android/en-US/changelogs/40101040.txt | 2 ++ 2 files changed, 3 insertions(+), 10 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/40101040.txt diff --git a/CHANGES.md b/CHANGES.md index 996e59a00c..40228f7db8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,6 @@ -Changes in Element 1.1.4 (2021-XX-XX) +Changes in Element 1.1.4 (2021-04-09) =================================================== -Features ✨: - - - Improvements 🙌: - Split network request `/keys/query` into smaller requests (250 users max) (#2925) - Crypto improvement | Bulk send NO_OLM withheld code @@ -30,9 +27,6 @@ Bugfix 🐛: - 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) -Translations 🗣: - - - SDK API changes ⚠️: - Several Services have been migrated to coroutines (#2449) - Removes filtering options on Timeline. @@ -40,9 +34,6 @@ SDK API changes ⚠️: Build 🧱: - Properly exclude gms dependencies in fdroid build flavour which were pulled in through the jitsi SDK (#3125) -Test: - - - Other changes: - Add version details on the login screen, in debug or developer mode - Migrate Retrofit interface to coroutine calls 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