Threema hace 3 años
padre
commit
37a89670db
Se han modificado 100 ficheros con 10944 adiciones y 8535 borrados
  1. BIN
      app/assets/emojis/activity-0.png
  2. BIN
      app/assets/emojis/activity-1.png
  3. BIN
      app/assets/emojis/food-0.png
  4. BIN
      app/assets/emojis/nature-0.png
  5. BIN
      app/assets/emojis/objects-0.png
  6. BIN
      app/assets/emojis/people-0.png
  7. BIN
      app/assets/emojis/people-1.png
  8. BIN
      app/assets/emojis/people-2.png
  9. BIN
      app/assets/emojis/people-3.png
  10. BIN
      app/assets/emojis/people-4.png
  11. BIN
      app/assets/emojis/people-5.png
  12. 37 0
      app/assets/emojis/search-index/be.csv
  13. 37 0
      app/assets/emojis/search-index/ca.csv
  14. 37 0
      app/assets/emojis/search-index/cs.csv
  15. 37 0
      app/assets/emojis/search-index/de.csv
  16. 11 0
      app/assets/emojis/search-index/diversities.csv
  17. 37 0
      app/assets/emojis/search-index/en.csv
  18. 37 0
      app/assets/emojis/search-index/es.csv
  19. 37 0
      app/assets/emojis/search-index/fr.csv
  20. 37 0
      app/assets/emojis/search-index/hu.csv
  21. 37 0
      app/assets/emojis/search-index/it.csv
  22. 37 0
      app/assets/emojis/search-index/nl.csv
  23. 37 0
      app/assets/emojis/search-index/no.csv
  24. 3330 3238
      app/assets/emojis/search-index/orders.csv
  25. 37 0
      app/assets/emojis/search-index/pl.csv
  26. 37 0
      app/assets/emojis/search-index/pt.csv
  27. 37 0
      app/assets/emojis/search-index/ru.csv
  28. 37 0
      app/assets/emojis/search-index/sk.csv
  29. 37 0
      app/assets/emojis/search-index/tr.csv
  30. 37 0
      app/assets/emojis/search-index/uk.csv
  31. BIN
      app/assets/emojis/symbols-0.png
  32. BIN
      app/assets/emojis/travel-0.png
  33. 7 7
      app/build.gradle
  34. 8 8
      app/src/androidTest/java/ch/threema/app/voip/SdpTest.java
  35. 2 2
      app/src/main/java/androidx/core/app/FixedJobIntentService.java
  36. 1 2
      app/src/main/java/ch/threema/app/ThreemaApplication.java
  37. 6 33
      app/src/main/java/ch/threema/app/activities/ContactDetailActivity.java
  38. 27 13
      app/src/main/java/ch/threema/app/activities/DirectoryActivity.java
  39. 1 2
      app/src/main/java/ch/threema/app/activities/DisableBatteryOptimizationsActivity.java
  40. 4 43
      app/src/main/java/ch/threema/app/activities/GroupDetailActivity.java
  41. 10 6
      app/src/main/java/ch/threema/app/activities/HomeActivity.java
  42. 3 0
      app/src/main/java/ch/threema/app/activities/StorageManagementActivity.java
  43. 1 14
      app/src/main/java/ch/threema/app/activities/ThreemaActivity.java
  44. 5 0
      app/src/main/java/ch/threema/app/activities/wizard/WizardStartActivity.java
  45. 34 11
      app/src/main/java/ch/threema/app/adapters/ContactDetailAdapter.java
  46. 3 2
      app/src/main/java/ch/threema/app/archive/ArchiveActivity.java
  47. 1 4
      app/src/main/java/ch/threema/app/asynctasks/DeleteConversationsAsyncTask.java
  48. 12 5
      app/src/main/java/ch/threema/app/asynctasks/DeleteIdentityAsyncTask.java
  49. 30 20
      app/src/main/java/ch/threema/app/backuprestore/csv/RestoreService.java
  50. 20 12
      app/src/main/java/ch/threema/app/camera/CameraFragment.kt
  51. 12 8
      app/src/main/java/ch/threema/app/dialogs/MessageDetailDialog.java
  52. 1 1
      app/src/main/java/ch/threema/app/dialogs/RateDialog.java
  53. 45 10
      app/src/main/java/ch/threema/app/dialogs/TextWithCheckboxDialog.java
  54. 4609 4346
      app/src/main/java/ch/threema/app/emojis/EmojiParser.java
  55. 404 332
      app/src/main/java/ch/threema/app/emojis/EmojiSpritemap.java
  56. 2 2
      app/src/main/java/ch/threema/app/emojis/search/EmojiSearchIndex.kt
  57. 156 6
      app/src/main/java/ch/threema/app/fragments/ComposeMessageFragment.java
  58. 1 1
      app/src/main/java/ch/threema/app/fragments/ContactsSectionFragment.java
  59. 24 12
      app/src/main/java/ch/threema/app/fragments/MyIDFragment.java
  60. 15 2
      app/src/main/java/ch/threema/app/jobs/ReConnectJobService.java
  61. 2 2
      app/src/main/java/ch/threema/app/mediaattacher/MediaAttachActivity.java
  62. 1 0
      app/src/main/java/ch/threema/app/mediaattacher/MediaSelectionActivity.java
  63. 5 1
      app/src/main/java/ch/threema/app/mediaattacher/MediaSelectionBaseActivity.java
  64. 2 0
      app/src/main/java/ch/threema/app/preference/SettingsAboutFragment.kt
  65. 17 27
      app/src/main/java/ch/threema/app/preference/SettingsActivity.kt
  66. 2 0
      app/src/main/java/ch/threema/app/preference/SettingsAppearanceFragment.kt
  67. 2 0
      app/src/main/java/ch/threema/app/preference/SettingsCallsFragment.kt
  68. 2 0
      app/src/main/java/ch/threema/app/preference/SettingsChatFragment.kt
  69. 5 0
      app/src/main/java/ch/threema/app/preference/SettingsDeveloperFragment.java
  70. 10 15
      app/src/main/java/ch/threema/app/preference/SettingsFragment.kt
  71. 2 0
      app/src/main/java/ch/threema/app/preference/SettingsMediaFragment.kt
  72. 5 0
      app/src/main/java/ch/threema/app/preference/SettingsNotificationsFragment.java
  73. 2 0
      app/src/main/java/ch/threema/app/preference/SettingsPrivacyFragment.kt
  74. 5 0
      app/src/main/java/ch/threema/app/preference/SettingsRateFragment.java
  75. 5 0
      app/src/main/java/ch/threema/app/preference/SettingsSecurityFragment.java
  76. 17 0
      app/src/main/java/ch/threema/app/preference/SettingsTroubleshootingFragment.java
  77. 24 4
      app/src/main/java/ch/threema/app/preference/ThreemaPreferenceFragment.kt
  78. 1 1
      app/src/main/java/ch/threema/app/services/AvatarCacheServiceImpl.java
  79. 1 1
      app/src/main/java/ch/threema/app/services/LocaleServiceImpl.java
  80. 9 3
      app/src/main/java/ch/threema/app/services/MessageService.java
  81. 45 7
      app/src/main/java/ch/threema/app/services/MessageServiceImpl.java
  82. 5 4
      app/src/main/java/ch/threema/app/services/NotificationActionService.java
  83. 2 1
      app/src/main/java/ch/threema/app/services/NotificationServiceImpl.java
  84. 1 4
      app/src/main/java/ch/threema/app/ui/DirectoryDataSource.java
  85. 99 0
      app/src/main/java/ch/threema/app/ui/ReportSpamView.kt
  86. 8 0
      app/src/main/java/ch/threema/app/utils/ConfigUtils.java
  87. 790 0
      app/src/main/java/ch/threema/app/utils/UnicodeUtil.kt
  88. 0 64
      app/src/main/java/ch/threema/app/utils/UrlUtil.java
  89. 125 0
      app/src/main/java/ch/threema/app/utils/UrlUtil.kt
  90. 129 122
      app/src/main/java/ch/threema/app/voip/PeerConnectionClient.java
  91. 7 2
      app/src/main/java/ch/threema/app/voip/VoipBluetoothManager.java
  92. 17 13
      app/src/main/java/ch/threema/app/voip/activities/CallActivity.java
  93. 12 12
      app/src/main/java/ch/threema/app/voip/activities/WebRTCDebugActivity.java
  94. 35 35
      app/src/main/java/ch/threema/app/voip/services/VoipCallService.java
  95. 4 4
      app/src/main/java/ch/threema/app/voip/services/VoipStateService.java
  96. 5 5
      app/src/main/java/ch/threema/app/webclient/services/instance/message/receiver/AcknowledgeRequestHandler.java
  97. 39 16
      app/src/main/res/layout/activity_directory.xml
  98. 29 1
      app/src/main/res/layout/dialog_message_detail.xml
  99. 9 0
      app/src/main/res/layout/fragment_compose_message.xml
  100. 50 49
      app/src/main/res/layout/header_contact_detail.xml

BIN
app/assets/emojis/activity-0.png


BIN
app/assets/emojis/activity-1.png


BIN
app/assets/emojis/food-0.png


BIN
app/assets/emojis/nature-0.png


BIN
app/assets/emojis/objects-0.png


BIN
app/assets/emojis/people-0.png


BIN
app/assets/emojis/people-1.png


BIN
app/assets/emojis/people-2.png


BIN
app/assets/emojis/people-3.png


BIN
app/assets/emojis/people-4.png


BIN
app/assets/emojis/people-5.png


+ 37 - 0
app/assets/emojis/search-index/be.csv

@@ -66,6 +66,43 @@
 〰️|працяжнік|пунктуацыя|хвалісты
 ㊗️|віншаванні|віншую|вітанні|ідэаграма «віншаванні» ў квадраце|кітайскае|пісьмо|японскае
 ㊙️|ідэаграма «сакрэтна» ў крузе|кітайскае|пісьмо|сакрэтна|японскае
+🥹|горды|засмучаны|злы|плача|стрымлівае слёзы|стрымліваецца
+🧌|казка|пачвара|троль|фантастычны
+🩻|доктар|косткі|медыцына|рэнтген|шкілет
+🩼|апора для хадзьбы|інваліднасць|калецтва|кастыль|кій|мыліца|палка
+🪩|блікі|вечарына|гулянка|дыскатэка|люстраны шар|танцы
+🪪|бяспека|дакумент|пасведчанне асобы
+🪫|нізкі зарад|электроніка
+🪬|амулет|ахова|марыя|мірыям|рука|фатыма|хамса
+🪷|будызм|в’етнам|індуізм|індыя|кветка|лотас|чысціня
+🪸|акіян|карал|рыф
+🪹|гнездаванне|пустое гняздо
+🪺|гнездаванне|гняздо з яйкамі
+🫃|жывот|поўны|раздзьмуты|цяжарны мужчына
+🫄|жывот|поўны|раздзьмуты|цяжарны чалавек
+🫅|вялікасць|знаць|кароль|манарх|цар|чалавек з каронай
+🫗|ліць вадкасць|напой|пустая|разліваць|шклянка
+🫘|бабовыя|боб|ежа|нырка
+🫙|ёмістасць|крама|прыправа|пустая|слоік|соус
+🫠|вадкі|знікае|растае|раствараецца|твар растае
+🫡|аддаць чэсць|вайсковец|вясёлы|салют|так|твар салютуе
+🫢|захоплены|зачараваны|збянтэжаны|здзіўлены|недаверлівы|спалоханы|твар з расплюшчанымі вачыма і закрытым рукой ротам
+🫣|закрыты твар падглядае адным вокам|зачараваны|падглядаць|утаропіцца
+🫤|незадаволены|няўпэўнены|расчараваны|скептычны|твар са скошаным ротам|хм
+🫥|дэпрэсіўны|знікае|інтраверт|невідзімка|твар, намаляваны пункцірам|хаваецца
+🫦|дыскамфорт|закусванне губы|нервовасць|неспакой|страх|трывога|флірт
+🫧|бурбалкі|газы|мыла|пад вадой|чысціня
+🫰|грошы|дорага|каханне|пстрычка|рука са скрыжаванымі вялікім і ўказальным пальцамі|сэрца
+🫱|направа|права|рука
+🫲|лева|налева|рука
+🫳|адпусціць|кінуць|прэч|псік|рука далонню ўніз
+🫴|лавіць|падазваць|падзываць|падыдзі|прапанова|рука далонню ўверх|хадзі сюды
+🫵|ты|указальны палец накіраваны на гледача|указанне
+🫶|каханне|любоў|рукі ў форме сэрца
+🛝|гульня|дзіцячая горка|парк адпачынку
+🛞|абарот|кола|круг|пакрышка|шына
+🛟|бяспека|круг|ратавальны круг|ратавальны сродак|ратаванне|сродак ратавання
+🟰|матэматыка|роўнасць|тоўсты знак роўнасці
 😀|зубы|усмешка|шырока ўсміхаецца
 😃|адкрыты|рот|усмешка|усміхаецца з адкрытым ротам
 😄|вочы|рот|усмешка|усміхаецца, адкрыўшы рот і прыплюшчыўшы вочы

+ 37 - 0
app/assets/emojis/search-index/ca.csv

@@ -66,6 +66,43 @@
 〰️|guió|ondulat|puntuació
 ㊗️|botó japonès "enhorabona"|enhorabona|ideograma|japonès|kanji
 ㊙️|"secret"|botó japonès "secret"|ideograma|japonès|kanji
+🥹|cara que s’aguanta les llàgrimes|dolor|emoció|enuig|orgull|resistir|tristesa
+🧌|conte|fades|fantasia|monstre|trol
+🩻|diagnòstic|esquelet|medicina|ossos|radiografia
+🩼|ajuda a la mobilitat|bastó|crossa|discapacitat|lesió
+🪩|ballar|bola de miralls|brillant|discoteca|festa
+🪪|carnet|credencials|identificació|seguretat|targeta d’identificació
+🪫|bateria baixa|electrònica|poca bateria
+🪬|amulet|mà de fàtima|mà de míriam|protecció
+🪷|budisme|flor|hinduisme|índia|lotus|puresa|vietnam
+🪸|corall|escullera|mar|oceà
+🪹|covar|niu buit
+🪺|covar|niu amb ous|ous
+🫃|embaràs|embarassat|home embarassat|panxa|prenyat
+🫄|embaràs|embarassada|embarassat|panxa|persona embarassada|prenyada|prenyat
+🫅|monarca|noblesa|persona amb corona|reialesa
+🫗|abocar|beguda|buit|got que aboca un líquid|rajar|vessar
+🫘|fesols|llegum|menjar|mongeta|ronyó
+🫙|buit|condiment|contenidor|emmagatzemar|pot|salsa|vidre
+🫠|cara que es desfà|desaparèixer|desfer-se|dissoldre’s|fondre’s|líquid
+🫡|cara que saluda|exèrcit|ordre|salutació|sí|val
+🫢|al·lucinar|cara amb els ulls oberts i la mà sobre la boca|increïble|sorpresa|vergonya
+🫣|cara tapada que espia amb un ull|espiar|fascinació|mirada|mirar
+🫤|cara amb la boca en diagonal|cara d’escepticisme|cara d’inseguretat|cara de decepció|decepcionat|escèptic|insegur|meh
+🫥|amagar-se|cara amb línia de punts|depressió|desaparèixer|introversió|invisible
+🫦|boca que es mossega el llavi|flirteig|incomoditat|nervis|por|preocupació
+🫧|bombolles|net|sabó|subaquàtic|submarí
+🫰|car|diners|fregar els dits|mà amb el dit índex i el polze creuats|petar els dits
+🫱|a la dreta|dreta|mà cap a la dreta
+🫲|a l’esquerra|esquerra|mà cap a l’esquerra
+🫳|abaixa|descarta|mà amb el palmell cap avall|no|rebutja
+🫴|acosta’t|dona’m|mà amb el palmell cap amunt|ofereix|vine
+🫵|apuntar|assenyalar|índex que assenyala l’espectador|tu
+🫶|amor|mans que formen un cor
+🛝|divertit|jugar|lliscar|parc|togoban
+🛞|cercle|girar|pneumàtic|rodar
+🛟|cèrcol|flotador|rescat|salvament|salvavides|seguretat
+🟰|igualtat|matemàtiques|signe d’igual gran
 😀|cara molt somrient|rialla|somriure|ulls
 😃|boca oberta|cara molt somrient amb els ulls ben oberts|somriure
 😄|boca oberta|cara amb rialla i els ulls somrients|somriure

+ 37 - 0
app/assets/emojis/search-index/cs.csv

@@ -66,6 +66,43 @@
 〰️|čára|dlouhá vlnovka|interpunkce|interpunkční|pomlčka|vlnitá|vlny|znaménka
 ㊗️|blahopřejeme|gratulace|ideogram|japonština|štěstí|štítek s japonským znakem blahopřání|祝
 ㊙️|ideogram|japonština|štítek s japonským znakem tajné|tajemství|utajení|秘
+🥹|dojetí|hněv|hrdost|obličej se slzami na krajíčku|pláč|sebeovládání|smutek
+🧌|fantasy|monstrum|obluda|obr|pohádka|příšera|troll
+🩻|doktor|kosti|kostra|lékař|medicína|rentgen|vyšetření|zdravotnictví
+🩼|berla|berle|hůl|invalidní|opora|postižení|zdravotní pomůcka|zranění
+🪩|disco|diskotéka|mirrorball|party|tanec|třpytky|zrcadlová koule
+🪪|identifikace|licence|osobní údaje|průkazka|totožnost|zabezpečení
+🪫|elektrická|elektřina|elektronika|málo energie|slabá baterie|vybitá
+🪬|amulet|fátima|fátimina|fátimy|hamsa|marie|ochrana|ruka
+🪷|buddhismus|čistota|hinduismus|indie|květina|lotos|vietnam
+🪸|korál|oceán|útes
+🪹|hnízdění|prázdné hnízdo
+🪺|hnízdění|hnízdo s vejci
+🫃|břicho|nadmutý|plný|těhotenství|těhotný muž
+🫄|břicho|nadmutá|nadmutý|plná|plný|těhotenství|těhotná osoba|těhotný
+🫅|člověk s korunou na hlavě|královna|královský|monarcha|panovník|veličenstvo|vládce
+🫗|nápoj|pití|sklenice|vylévání tekutiny|vylít|vylití|vyprázdnění|vyprázdnit
+🫘|fazole|jídlo|ledviny|luštěniny
+🫙|dóza|koření|kořenka|nádoba|omáčka|prázdná|skladování
+🫠|mizení|rozpouštění|roztékání|tající obličej|tání
+🫡|ano|pozdrav|rozkaz|salutování|salutující obličej|slunečno|vojsko|zastíněné oči
+🫢|nevěřícnost|nevíra|obličej s otevřenýma očima a dlaní před pusou|pohoršení|překvapení|trapas|údiv|úlek|úžas
+🫣|nakukování|obličej vykukující jedním okem|uchvácení|vykouknutí|zaujetí|zírání
+🫤|ech|nejistý|nerozhodný|obličej s pusou našikmo|pf|skeptický|zklamání
+🫥|deprese|introvert|mizící|neviditelný|obličej s tečkovaným obrysem|schovaný|skrytý
+🫦|flirtování|nejistota|nervozita|obava|skousnutý ret|starost|strach|svádění
+🫧|bubliny|čisté|čistota|krknutí|mydliny|mýdlo|pod vodou|říhnutí
+🫰|drahé|drahota|láska|lusknutí|peníze|ruka se zkříženým palcem a ukazováčkem|srdce
+🫱|doprava|pravá|ruka otočená doprava|vpravo
+🫲|doleva|levá|ruka otočená doleva|vlevo
+🫳|huš|kšá|odehnání|puštění|ruka otočená dlaní dolů|upuštění|zamítnutí
+🫴|blíž|chycení|nabídka|pojď|pokynutí|ruka otočená dlaní vzhůru
+🫵|ty tam|ukazováček namířený na diváka|ukazovat|vy tam
+🫶|láska|ruce spojené do tvaru srdce
+🛝|dětská skluzavka|hra|hřiště|zábavní park
+🛞|kolo|kruh|kulatý|otáčení|pneumatika
+🛟|bezpečí|plavací|plavání|plavčík|plovák|záchrana|záchranný kruh
+🟰|matematika|rovná se|rovnost|tučný znak rovnosti
 😀|smajlík|tvář|úšklebek|výraz|zubící se obličej
 😃|otevřený|smajlík|tvář|úsměv|ústa|výraz|zubící se obličej s velkýma očima
 😄|oko|otevřený|smajlík|tvář|úsměv|ústa|výraz|zubící se obličej s usměvavýma očima

+ 37 - 0
app/assets/emojis/search-index/de.csv

@@ -66,6 +66,43 @@
 〰️|gewellt|linie|wellenlinie
 ㊗️|gratulation|japanisches schriftzeichen|schriftzeichen für gratulation
 ㊙️|geheimnis|japanisches schriftzeichen|schriftzeichen für geheimnis
+🥹|aufgebracht|erbost|gesicht, das tränen zurückhält|tränen zurückhalten|traurig|weinen|zurückhalten
+🧌|fantasy|kobold|märchen|monster|troll|ungeheuer
+🩻|knochen|medizin|radiologie|röntgenbild|skelett|arzt|bildgebung
+🩼|behinderung|gehhilfe|gehstütze|krücke|schmerzen|stock|verletzt
+🪩|discokugel|lichtreflexe|party|spiegelkugel|tanzen
+🪪|ausweis|führerschein|personalausweis|plakette|id|sicheriet
+🪫|elektronik|niedriger akkustand|schwache batterie|schwacher akku|akku leer|batterie leer
+🪬|amulett|fatima|glückssymbol|hamsa|hand|maria|miriam|schutz
+🪷|blume|buddhismus|hinduismus|indien|lotusblüte|reinheit|vietnam
+🪸|korallenriff|ozean|riff
+🪹|leeres nest|nestbau|nisten|vogelnest
+🪺|nest mit eiern|nestbau|nisten|vogelnest
+🫃|aufgebläht|bauch|dick|schwangerer mann
+🫄|aufgebläht|bauch|dick|schwangere person
+🫅|adelig|königliche hoheit|monarchin|person mit krone
+🫗|ausgießen|flüssigkeit ausgießen|getränk|gießen|glas|leer|trinken|verschütten|ausgiessen|flüssigkeit ausgiessen|giessen
+🫘|bohnen|essen|hülsenfrucht|kidney-bohne|lebensmittel
+🫙|einmachglas|einweckglas|gewürzglas|leer|marmeladeglas
+🫠|auflösen|flüssig|gesicht|schmelzendes gesicht|verschwinden|verflüssigen
+🫡|aye, aye|gesicht|grüßendes gesicht|jawohl|militär|ok|respekt|salutieren|aye aye|grüssendes gesicht|truppen
+🫢|erschrecken|erstaunen|furcht|gesicht mit offenen augen und hand über dem mund|überraschung|unglauben|verlegenheit
+🫣|gebannt|gesicht mit durch die finger linsendem auge|linsen|spähen|starren
+🫤|enttäuscht|gesicht mit schrägem mund|langweilig|na ja|skeptisch|unsicher
+🫥|depressiv|gesicht mit gestrichelter linie|introvertiert|unsichtbar|verschwinden|verstecken
+🫦|angst|ängstlich|auf lippe beißen|besorgt|flirtend|nervös|unbehaglich|auf lippe beissen
+🫧|blasen|reinigen|seifenblasen|unter wasser|wasserblasen|sauber|schaum
+🫰|fingerherz|geld|hand mit gekreuztem zeigefinger und daumen|handgeste|teuer
+🫱|hand|nach rechts weisende hand|rechts
+🫲|hand|links|nach links weisende hand
+🫳|abweisen|hand mit handfläche nach unten|handgeste|hau ab|scheuchen|von sich weisen|wegscheuchen|wegschicken
+🫴|anbieten|bieten|darbieten|einladen|hand mit handfläche nach oben|handgeste|kommen|locken
+🫵|auf betrachter zeigender zeigefinger|du|handgeste|mit finger zeigen|sie|zeigen
+🫶|hände, die herz bilden|händeherz|handgeste|herz|liebe
+🛝|rutsche|spielen|spielplatzrutsche|vergnügungspark
+🛞|autorad|drehen|rad|reifen|rotieren
+🛟|leben retten|retten|rettungsring|schwimmring|sicherheit
+🟰|gleichheitszeichen extrafett|mathematik
 😀|gesicht|grinsendes gesicht|lol|lustig
 😃|gesicht|grinsendes gesicht mit großen augen|lächeln|lol|lustig|grinsendes gesicht mit grossen augen
 😄|gesicht|grinsendes gesicht mit lachenden augen|lol|lustig

+ 11 - 0
app/assets/emojis/search-index/diversities.csv

@@ -2,6 +2,17 @@
 👐|👐🏻|👐🏼|👐🏽|👐🏾|👐🏿
 🙌|🙌🏻|🙌🏼|🙌🏽|🙌🏾|🙌🏿
 👏|👏🏻|👏🏼|👏🏽|👏🏾|👏🏿
+🫱|🫱🏻|🫱🏼|🫱🏽|🫱🏾|🫱🏿
+🫲|🫲🏻|🫲🏼|🫲🏽|🫲🏾|🫲🏿
+🫳|🫳🏻|🫳🏼|🫳🏽|🫳🏾|🫳🏿
+🫴|🫴🏻|🫴🏼|🫴🏽|🫴🏾|🫴🏿
+🫰|🫰🏻|🫰🏼|🫰🏽|🫰🏾|🫰🏿
+🫵|🫵🏻|🫵🏼|🫵🏽|🫵🏾|🫵🏿
+🫶|🫶🏻|🫶🏼|🫶🏽|🫶🏾|🫶🏿
+🤝|🤝🏻|🤝🏼|🤝🏽|🤝🏾|🤝🏿
+🫅|🫅🏻|🫅🏼|🫅🏽|🫅🏾|🫅🏿
+🫃|🫃🏻|🫃🏼|🫃🏽|🫃🏾|🫃🏿
+🫄|🫄🏻|🫄🏼|🫄🏽|🫄🏾|🫄🏿
 👍|👍🏻|👍🏼|👍🏽|👍🏾|👍🏿
 👎|👎🏻|👎🏼|👎🏽|👎🏾|👎🏿
 👊|👊🏻|👊🏼|👊🏽|👊🏾|👊🏿

+ 37 - 0
app/assets/emojis/search-index/en.csv

@@ -66,6 +66,43 @@
 〰️|dash|punctuation|wavy
 ㊗️|congratulations|ideograph|japanese congratulations button|祝
 ㊙️|secret|ideograph|japanese secret button|秘
+🥹|angry|cry|face holding back tears|proud|resist|sad
+🧌|fairy tale|fantasy|monster|troll
+🩻|bones|doctor|medical|skeleton|x-ray
+🩼|cane|crutch|disability|hurt|mobility aid|stick
+🪩|dance|disco|glitter|mirror ball|party
+🪪|credentials|identification card|license|security|driving|licence
+🪫|electronic|low battery|low energy
+🪬|amulet|fatima|hamsa|hand|mary|miriam|protection
+🪷|buddhism|flower|hinduism|india|lotus|purity|vietnam
+🪸|coral|ocean|reef
+🪹|empty nest|nesting
+🪺|nest with eggs|nesting
+🫃|belly|bloated|full|pregnant man
+🫄|belly|bloated|full|pregnant person
+🫅|monarch|noble|person with crown|regal|royalty
+🫗|drink|empty|glass|pouring liquid|spill
+🫘|beans|food|legume|kidney beans
+🫙|condiment|container|empty|jar|sauce|store
+🫠|disappear|dissolve|liquid|melting face
+🫡|ok|salute|saluting face|sunny|troops|yes
+🫢|amazement|awe|disbelief|embarrass|face with open eyes and hand over mouth|scared|surprise
+🫣|captivated|face with peeking eye|peep|stare
+🫤|disappointed|face with diagonal mouth|meh|skeptical|unsure|sceptical
+🫥|depressed|disappear|dotted line face|hide|introvert|invisible
+🫦|anxious|biting lip|fear|flirting|nervous|uncomfortable|worried
+🫧|bubbles|burp|clean|soap|underwater
+🫰|expensive|hand with index finger and thumb crossed|heart|love|money|snap
+🫱|hand|rightwards hand
+🫲|hand|leftwards hand
+🫳|dismiss|drop|palm down hand|shoo
+🫴|beckon|catch|come|offer|palm up hand
+🫵|index pointing at the viewer|point|you
+🫶|heart hands|love
+🛝|amusement park|playground slide
+🛞|circle|tire|turn|wheel|tyre
+🛟|float|life preserver|life saver|rescue|ring buoy|safety|buoy
+🟰|equality|heavy equals sign|maths
 😀|face|grinning face
 😃|face|grinning face with big eyes|mouth|open|smile
 😄|eye|face|grinning face with smiling eyes|mouth|open|smile

+ 37 - 0
app/assets/emojis/search-index/es.csv

@@ -66,6 +66,43 @@
 〰️|guion|marca de sonido largo|ondulado|línea ondulada|puntuación
 ㊗️|enhorabuena|ideograma japonés para "enhorabuena"|japonés|kanji|felicidades|"enhorabuena"|祝
 ㊙️|ideograma japonés para "secreto"|japonés|kanji|secreto|"secreto"|秘
+🥹|cara aguantándose las lágrimas|dolor|emocionado|enfadado|orgulloso|resistir|triste
+🧌|cuento de hadas|fantasía|monstruo|troll
+🩻|doctora|esqueleto|huesos|médica|médico|radiografía|rayos x|medicina
+🩼|ayuda para la movilidad|bastón|discapacidad|lesión|muleta|palo|ayuda movilidad
+🪩|bailar|bola de espejos|brillar|discoteca|fiesta|baile|brillante
+🪪|carné de conducir|carné de identidad|credenciales|identificación|permiso|seguridad|licencia|tarjeta de identificación
+🪫|batería baja|carga baja|electrónico|energía baja|pila|baja energía|electrónica
+🪬|amuleto|fátima|hamsa|jamsa|mano|maría|miriam|protección
+🪷|budismo|hinduismo|india|loto|pureza|vietnam|lotus|flor de loto
+🪸|arrecife|coral|océano
+🪹|anidación|anidamiento|anidar|nido vacío
+🪺|anidación|anidamiento|anidar|nido con huevos
+🫃|barriga|embarazado|hinchado|hinchazón|hombre embarazado|inflado|lleno|estómago
+🫄|barriga|embarazo|gestación|hinchazón|persona embarazada|embarazada|estómago|hinchado|lleno
+🫅|monarca|monarquía|noble|persona con corona|realeza
+🫗|bebida|derramar|líquido derramándose|vacío|vaso|verter|copa|derramada|líquido que se vierte
+🫘|alubias|comida|habichuela roja|habichuelas|judía roja|judías|legumbre|frijoles|riñón
+🫙|almacenar|condimento|frasco|recipiente|salsa|tarro|vacío|jarra|contenedor|guardar
+🫠|calor|cara derritiéndose|derretido|derretirse|desaparecer|fundirse|líquido|cara que se derrite|disolverse
+🫡|cara saludando|ejército|orden|saludo|sí|vale|ok|si|soleado|tropas
+🫢|alucinar|asombro|cara con ojos abiertos y boca tapada|increíble|sorpresa|vergüenza|austado|no creíble
+🫣|cara tapada con ojo espiando|espiar|fascinado|mirar|vistazo|capturado
+🫤|cara con boca diagonal|decepción|decepcionado|escéptico|inseguro|jo|vaya
+🫥|cara con línea de puntos|depresivo|desaparecer|esconderse|indivisible|introvertido
+🫦|ansioso|incómodo|labio mordido|ligar|miedo|nervioso|preocupado|ansiedad|coquetería|incomodidad|mordiendo el labio|nerviosismo|preocupación|coquetear
+🫧|bajo el agua|burbujas|eructar|jabón|limpiar|limpio|repetir
+🫰|amor|caro|chasquido|corazón|dinero|mano con dedo índice y pulgar cruzados|tronar los dedos
+🫱|a la derecha|derecha|mano hacia la derecha
+🫲|a la izquierda|izquierda|mano hacia la izquierda
+🫳|bajar|descartar|fuera|mano con la palma hacia abajo|no|rechazar|espantar|quitar
+🫴|acércate|dame|mano con la palma hacia arriba|ofrecer|trae|ven
+🫵|apuntar|dedo índice apuntándote a ti|ti|tú
+🫶|amor|manos formando un corazón|corazón con las manos
+🛝|jugar|parque de atracciones|tobogán|resbaladilla
+🛞|círculo|girar|neumático|rodar|rueda|llanta
+🛟|flotador|rescate|salvavidas|seguridad|socorrista
+🟰|equivalencia|igualdad|matemáticas|mates|signo igual grueso|signo igual resaltado
 😀|cara sonriendo|divertido|feliz|sonrisa|cara feliz|contento
 😃|cara sonriendo con ojos grandes|divertido|risa|sonriendo|boca abierta|sonrisa con boca abierta|abierta
 😄|abierta|cara sonriendo con ojos sonrientes|ojo|sonrisa|boca abierta|boca y ojos sonrientes|divertido|risa

+ 37 - 0
app/assets/emojis/search-index/fr.csv

@@ -66,6 +66,43 @@
 〰️|ligne ondulée|ponctuation|trait d’union ondulé|vague
 ㊗️|bouton félicitations en japonais|félicitations|japonais|idéogramme|symbole japonais signifiant « félicitations »|祝
 ㊙️|bouton secret en japonais|japonais|secret|idéogramme|symbole japonais signifiant « secret »|秘
+🥹|pleurs|retenir ses larmes|triste|visage retenant ses larmes
+🧌|conte de fées|légende|monstre|mythe|troll
+🩻|docteur|médical|os|poumons|radiographie|rayons x|squelette
+🩼|accident|béquille|blessé|boiter|canne|handicap
+🪩|boule à facettes|briller|danse|disco|fête
+🪪|carte d’identité|justificatif|papiers|permis de conduire|pièce d’identité|sécurité
+🪫|batterie|déchargé|électronique|énergie faible|pile faible
+🪬|amulette|fatma|hamsa|khamsa|main de fatma|myriam|protection
+🪷|bouddhisme|fleur|hindouisme|inde|lotus|pureté|viêt nam
+🪸|corail|océan|récif
+🪹|faire son nid|nid vide
+🪺|faire son nid|nid avec œufs
+🫃|enceinte|grossesse|homme enceinte|ventre
+🫄|enceinte|grossesse|personne enceinte|ventre
+🫅|monarque|noble|personne avec une couronne|royal|royauté
+🫗|boisson|renverser|verre|verser un liquide|vider
+🫘|haricots|légume|nourriture|rognon|fèves|haricot rouge|légumineuse
+🫙|bocal|condiments|conserver|récipient|sauce|vide
+🫠|disparaître|fondre|liquéfié|liquide|se dissoudre|visage qui fond
+🫡|armée|compris|ok|oui|salut|visage qui fait un salut militaire
+🫢|embarrassé|étonné|gêné|impressionné|stupéfait|surpris|visage avec yeux ouverts et main sur la bouche
+🫣|captivé|fasciné|jeter un coup d’œil|regarder fixement|visage qui regarde entre ses doigts
+🫤|déçu|euh|incertain|sceptique|visage avec bouche en diagonale
+🫥|déprimé|disparaître|introverti|invisible|se cacher|visage en pointillés
+🫦|anxieux|flirt|inquiet|lèvres qui se mordent|mal à l’aise|nerveux
+🫧|bulles|propre|savon|sous l’eau
+🫰|amour|argent|cher|claquer des doigts|cœur|main avec index et pouce croisés
+🫱|droite|main vers la droite|vers la droite
+🫲|gauche|main vers la gauche|vers la gauche
+🫳|chasser|faire partir|laisser tomber|main paume vers le bas|ouste|rejeter
+🫴|appeler de la main|attirer|faire venir|main paume vers le haut|offre
+🫵|index pointant vers l’utilisateur|pointer|toi
+🫶|amour|cœur|mains qui forment un cœur
+🛝|aire de jeu|jouer|toboggan|glissade
+🛞|cercle|pneu|roue|tourner
+🛟|bouée de sauvetage|flotter|sauver|sauveteur|secours|sécurité
+🟰|égalité|mathématiques|signe égal gras
 😀|sourire|visage rieur|grand sourire|large sourire|visage avec large sourire
 😃|sourire|visage souriant avec de grands yeux|bouche ouverte|visage souriant les yeux grands ouverts
 😄|sourire|visage très souriant aux yeux rieurs|bouche ouverte|œil|visage avec large sourire et yeux rieurs

+ 37 - 0
app/assets/emojis/search-index/hu.csv

@@ -66,6 +66,43 @@
 〰️|gondolatjel|hullámos|központozás
 ㊗️|gratuláció|japán gratuláció gomb|képírásjel|祝
 ㊙️|titok|japán titok gomb|képírásjel|秘
+🥹|büszke|dühös|ellenáll|könnyeivel küszködő arc|sír|szomorú
+🧌|képzelet|szörny|troll|tündérmese
+🩻|csontok|csontváz|orvosi|röntgen
+🩼|bot|fogyatékosság|mankó|mozgássegítő|sérülés|sétapálca
+🪩|buli|csillám|diszkó|tánc|tükörgömb
+🪪|azonosító|biztonság|engedély|hitelesítő adatok|személyi igazolvány
+🪫|alacsony energiaszint|alacsony töltöttség|elektronikus
+🪬|amulett|fatima|hamsza|kéz|mária|mirjam|védelem
+🪷|buddhizmus|hinduizmus|india|lótusz|tisztaság|vietnam|virág
+🪸|korall|óceán|zátony
+🪹|fészekrakás|üres fészek
+🪺|fészek tojásokkal|fészekrakás
+🫃|has|puffadt|teli|terhes férfi
+🫄|has|puffadt|teli|terhes személy
+🫅|koronás személy|méltóság|nemes|uralkodói
+🫗|ital|kiönt|ömlő folyadék|pohár|üres
+🫘|bab|étel|hüvelyes növény|vese
+🫙|befőttesüveg|fűszer|szósz|tárolás|tároló|üres
+🫠|elolvadó arc|eloszlik|eltűnik|folyékony|olvad
+🫡|derűs|igen|katonák|oké|tisztelgés|tisztelgő arc
+🫢|ámulat|csodálkozás|hitetlenkedés|ijedt|meglep|nyitott szemű, száját takaró arc|zavarba hoz
+🫣|bámul|kémlelő szemű arc|megbabonázott|pillant
+🫤|átlós szájú arc|azta|bizonytalan|csalódott|kétkedő
+🫥|elrejtőzik|eltűnik|introvertált|láthatatlan|levert|pontozott vonalas arc
+🫦|aggódó|ajkába harapó száj|félelem|flört|ideges|kényelmetlen|nyugtalan
+🫧|böfi|buborékok|szappan|tiszta|víz alatti
+🫰|csettint|drága|keresztezett mutató- és hüvelykujjú kéz|pénz|szeretet|szív
+🫱|jobbra néző kéz|kéz
+🫲|balra néző kéz|kéz
+🫳|elejt|hess|lefelé néző kéz|mellőz
+🫴|elkap|felajánl|felfelé néző kéz|hív|int
+🫵|felhasználóra mutató ujj|mutat|te
+🫶|szeretet|szívet formázó kezek
+🛝|játék|játszótéri csúszda|vidámpark
+🛞|abroncs|fordul|kerék|kör
+🛟|biztonság|életmentő|lebeg|megment|mentőöv
+🟰|egyenlőség|matematika|vastag egyenlőségjel
 😀|arc|nevetés|nevető arc
 😃|arc|mosoly|nagyra nyílt szemmel mosolygó arc|nyitott száj|vidámság
 😄|arc|mosoly|nevető arc mosolygó szemmel|száj|szem

+ 37 - 0
app/assets/emojis/search-index/it.csv

@@ -66,6 +66,43 @@
 〰️|ondulato|trattino
 ㊗️|congratulazioni|giapponese|ideogramma giapponese di congratulazioni
 ㊙️|giapponese|ideogramma giapponese di segreto|segreto
+🥹|arrabbiato|faccina che trattiene le lacrime|fiero|piangere|resistere|triste
+🧌|fantasia|favola|fiaba|mostro|troll
+🩻|dottore|lastra|medico|ossa|radiografia|raggi x|scheletro
+🩼|ausilio|bastone|disabile|ferito|infortunio|mobilità|stampella|supporto
+🪩|ballare|brillare|dance|discoteca|festa|glitter|palla da discoteca|specchi
+🪪|badge|carta d’identità|credenziali|documento d’identità|patente|sicurezza|tessera
+🪫|batteria scarica|elettronica|energia bassa
+🪬|alo|amuleto|hamsa|khamsa|mano di fatima|maria|miriam|protezione
+🪷|buddhismo|fiore|india|induismo|loto|purezza|vietnam
+🪸|barriera corallina|corallo|oceano
+🪹|nidificazione|nido vuoto
+🪺|nidificazione|nido con uova
+🫃|gonfio|gravido|incinto|pancia|pieno|uomo incinto
+🫄|gonfio|gravido|incinto|pancia|persona incinta|pieno
+🫅|monarca|nobile|persona con corona|regale|regina
+🫗|bevanda|bicchiere|drink|liquido versato|versare
+🫘|cibo|fagioli|legume
+🫙|barattolo|condimento|conservare|contenitore|dispensa|salsa|sugo|vuoto
+🫠|dissolversi|faccina che si scioglie|liquefarsi|liquido|sciogliersi|sparire|squagliarsi
+🫡|faccina che fa il saluto|militare|ok|saluto|signorsì|soleggiato|truppe
+🫢|faccina con occhi aperti e mano sulla bocca|imbarazzo|incredulità|meraviglia|sorpresa|spavento|stupore
+🫣|faccina che sbircia tra le dita|fissare|incuriosito|sbirciare|spiare
+🫤|così così|deluso|dubbioso|faccina perplessa|meh|perplesso|scettico
+🫥|depresso|faccina tratteggiata|introverso|invisibile|nascondersi|scomparire|sparire
+🫦|ansia|bocca che morde il labbro|disagio|flirtare|nervoso|paura|preoccupato|sedurre
+🫧|bolle|bollicine|pulito|rutto|sapone|schiuma|sott’acqua|subacqueo
+🫰|amore|caro|costoso|cuore|denaro|mano con indice e pollice incrociati|schioccare|soldi
+🫱|destra|mano rivolta a destra|verso destra
+🫲|mano rivolta a sinistra|sinistra|verso sinistra
+🫳|cacciare|congedare|imporre|mano con il palmo verso il basso
+🫴|mano con il palmo verso l’alto|offrire|prendere|raccogliere|richiamare
+🫵|indicare|indice verso l’osservatore|puntare|tu
+🫶|amore|cuore|mani a cuore|ti amo|ti voglio bene
+🛝|area divertimento|area giochi|giocare|parco giochi|scivolo
+🛞|cerchio|girare|gomma|pneumatico|ruota
+🛟|ciambella|galleggiante|gonfiabile|salvagente|salvare|sicurezza|soccorso
+🟰|matematica|segno di uguaglianza|uguaglianza|uguale
 😀|faccina che sogghigna|faccina con un gran sorriso|risata|sogghignare
 😃|faccina con un gran sorriso e occhi spalancati|faccina sorridente|risata|sorridere
 😄|bocca aperta|faccina con sorriso e occhi sorridenti|faccina con un gran sorriso e occhi sorridenti|occhi felici|risata|sorriso

+ 37 - 0
app/assets/emojis/search-index/nl.csv

@@ -66,6 +66,43 @@
 〰️|golvend|lang geluid|streepje
 ㊗️|‘gefeliciteerd’|ideogram|japans teken voor ‘gefeliciteerd’|祝
 ㊙️|‘geheim’|ideogram|japans teken voor ‘geheim’|秘
+🥹|boos|gezicht dat tranen tegenhoudt|huilen|trots|verdrietig|verzetten
+🧌|fantasie|monster|sprookje|trol
+🩻|botten|dokter|medisch|röntgenfoto|skelet
+🩼|gewond|handicap|kruk|mobiliteitshulp|stok|wandelstok
+🪩|dans|disco|feest|glitter|spiegelbal
+🪪|beveiliging|id|legitimatiebewijs|vergunning
+🪫|batterij bijna leeg|electronisch|weinig batterijlading
+🪬|amulet|bescherming|fatima|hamsa|hand|maria|mirjam
+🪷|bloem|boeddhisme|hindoeïsme|india|lotus|puurheid|vietnam
+🪸|koraal|oceaan|rif
+🪹|leeg nest|nestelen
+🪺|nest met eieren|nestelen
+🫃|buik|opgeblazen|vol|zwangere man
+🫄|buik|opgeblazen|vol|zwanger persoon
+🫅|edele|heerser|iemand van koninklijken bloede|persoon met kroon|regaal
+🫗|drinken|glas|leeg|morsen|vloeistof uitgieten
+🫘|bonen|kidneyboon|peulvrucht|voedsel
+🫙|bewaren|leeg|pot|saus|specerij|verpakking
+🫠|oplossen|smeltend gezicht|verdwijnen|vloeibaar
+🫡|ja|oké|saluerend gezicht|saluut|troepen|zonnig
+🫢|bang|gezicht met open ogen en hand over de mond|ongeloof|ontzag|schaamte|verbazing|verrassing
+🫣|geboeid|gezicht met glurend oog|gluren|staren
+🫤|gezicht met schuine mond|meh|onzeker|sceptisch|teleurgesteld
+🫥|depressief|gezicht in stippellijn|introvert|onzichtbaar|verbergen|verdwijnen
+🫦|angst|bezorgd|flirten|mond die op lip bijt|nerveus|ongemakkelijk|ongerust
+🫧|bubbel|onderwater|schoon|zeepbellen
+🫰|duur|geld|hand met wijsvinger en duim gekruist|hart|liefde|vingerknip
+🫱|hand naar rechts|naar rechts|rechts
+🫲|hand naar links|links|naar links
+🫳|handpalm omlaag|laten vallen|wegjagen|wegsturen
+🫴|aanbieden|handpalm omhoog|kom|vangen|wenken
+🫵|jij|wijsvinger die naar de kijker wijst|wijzen
+🫶|handen in de vorm van een hart|liefde
+🛝|glijbaan|speeltuin|spelen
+🛞|cirkel|draaien|wiel
+🛟|drijven|redden|reddingsboei|reddingsgordel|reddingsvest|veiligheid
+🟰|gelijkheid|vetgedrukt gelijkteken|wiskunde
 😀|gezicht|grijns|grijnzend gezicht
 😃|gezicht|grijnzend gezicht met grote ogen|lach|mond|open
 😄|gezicht|grijnzend gezicht met lachende ogen|lach|mond|oog|open

+ 37 - 0
app/assets/emojis/search-index/no.csv

@@ -66,6 +66,43 @@
 〰️|bølgestrek|bølget bindestrek|tegn for lang lyd
 ㊗️|«gratulerer» på japansk|gratulerer|ideograf|japansk|祝
 ㊙️|«hemmelig» på japansk|hemmelig|ideograf|japansk|秘
+🥹|fjes som holder tilbake tårene|gråte|motstå|rørt|sint|stolt|trist
+🧌|eventyr|fantasi|fantasy|monster|troll
+🩻|bein|doktor|lege|ribbein|ribben|røntgen|skjelett|undersøkelse
+🩼|hjelpemiddel|krykke|pinne|skadet|stokk|ufør
+🪩|dans|disco|diskokule|diskotek|fest|glitter|speilkule
+🪪|adgangskort|førerkort|id-kort|identifikasjon|legitimasjon|sikkerhet
+🪫|elektronikk|lavt batteri|lite batteri|lite energi
+🪬|amulett|beskyttelse|fatima|hamsa|hånd|lykkebringer|maria|miriam
+🪷|blomst|buddhisme|hinduisme|india|lotusblomst|renhet|vietnam
+🪸|dyr|hav|korallrev
+🪹|bygge rede|slå seg ned|tomt rede
+🪺|bygge rede|rede med egg|slå seg ned
+🫃|full mage|gravid|mann|mett|oppblåst
+🫄|full mage|gravid person|kjønnsnøytral|mett|oppblåst
+🫅|adel|kongelig|majestetisk|monark|person med krone
+🫗|drikke|glass|helle ut|helle væske|søle|tomt
+🫘|belgfrukt|bønner|kidneybønner|mat
+🫙|beholder|krukke|mos|oppbevare|pålegg|syltetøy|tom
+🫠|fjes som smelter|forsvinner|går i oppløsning|væske
+🫡|fjes som viser honnør|ja|militærhilsen|salutt
+🫢|fjes med åpne øyne og hånd over munnen|flau|forskrekkelse|overraskelse|redd
+🫣|fjes som titter frem bak hendene|klarer ikke dy seg|må se|stirre|titte
+🫤|fjes med diagonal munn|fjes med skråstrek|meh|skeptisk|skuffet|usikker
+🫥|fjes med stiplet omriss|forsvinner|gjemme seg|introvert|usynlig
+🫦|bekymret|bite seg i leppa|bite seg i leppen|flørte|nervøs|redd|ukomfortabel
+🫧|bobler|rap|rengjøre|rense|såpe|under vann|vaske
+🫰|dyrt|hånd med pekefinger og tommel krysset|hjerte|kjærlighet|knipse|penger
+🫱|hånd mot høyre|høyre
+🫲|hånd mot venstre|venstre
+🫳|avfeie|håndflaten ned|husj|slippe
+🫴|gripe|håndflaten opp|invitere|kalle på|lokke|tilby
+🫵|deg|pekefinger peker på leseren|peker på deg|uncle sam
+🫶|hender som lager hjerte|hjertehender|kjærlighet
+🛝|fornøyelsespark|lekeplass|sklie
+🛞|bildekk|dreie|felg|hjul|rulle|sirkel
+🛟|badering|flyte|livbøye|livbøyle|livredning|redningsaksjon|redningsbøye|redningsbøyle
+🟰|fett likhetstegn|likhetstegn i fet skrift|matematikk
 😀|fjes|glisefjes
 😃|åpen|fjes|glisende fjes med vidåpne øyne|latter|munn|smilefjes med åpen munn
 😄|latter|med tenner|morsomt|smilefjes med åpen munn og smilende øyne

+ 3330 - 3238
app/assets/emojis/search-index/orders.csv

@@ -1,3247 +1,3339 @@
-⁉️|3046
-ℹ️|3087
-↔️|3135
-↕️|3134
-↖️|3133
-↗️|3130
-↘️|3131
-↙️|3132
-⌨️|2726
-☀️|2115
-☁️|2119
-☂️|2133
-☃️|2126
-☄️|2110
-☑️|3166
-☔|2132
-☕|2234
-☘️|2065
-☠️|104
-☢️|3000
-☣️|3001
-☦️|2982
-☸️|2977
-☹️|41
-♈|2985
-♉|2986
-♐|2993
-♑|2994
-♒|2995
-♓|2996
-♠️|3213
-♣️|3214
-♥️|3215
-♦️|3216
-♨️|3033
-⚒️|2784
-⚓|2653
-⚔️|2801
-⚖️|2778
-⚗️|2813
-⚙️|2788
-✂️|2938
-✅|3056
-✈️|2637
-✉️|2880
-✒️|2941
-✔️|3165
-✖️|3150
-✡️|2978
-✨|2108
-✳️|3060
-✴️|3009
-❄️|2125
-❇️|3059
-❓|3043
-❔|3044
-❕|3042
-❗|3041
-❣️|2962
-❤️|2952
-➕|3147
-➖|3148
-➗|3149
-⤴️|3138
-⤵️|3139
-〰️|3157
-㊗️|3014
-㊙️|3013
+⁉️|3250
+ℹ️|3303
+↔️|3179
+↕️|3178
+↖️|3177
+↗️|3171
+↘️|3173
+↙️|3175
+⌨️|2973
+☀️|2769
+☁️|2777
+☂️|2792
+☃️|2797
+☄️|2799
+☑️|3266
+☔|2793
+☕|2560
+☘️|2446
+☠️|106
+☢️|3168
+☣️|3169
+☦️|3198
+☸️|3195
+☹️|77
+♈|3203
+♉|3204
+♐|3211
+♑|3212
+♒|3213
+♓|3214
+♠️|2874
+♣️|2877
+♥️|2875
+♦️|2876
+♨️|2644
+⚒️|3076
+⚓|2701
+⚔️|3079
+⚖️|3090
+⚗️|3098
+⚙️|3088
+✂️|3063
+✅|3265
+✈️|2710
+✉️|3024
+✒️|3038
+✔️|3267
+✖️|3243
+✡️|3194
+✨|2808
+✳️|3273
+✴️|3274
+❄️|2796
+❇️|3275
+❓|3251
+❔|3252
+❕|3253
+❗|3254
+❣️|137
+❤️|141
+➕|3244
+➖|3245
+➗|3246
+⤴️|3182
+⤵️|3183
+〰️|3255
+㊗️|3327
+㊙️|3328
 😀|1
 😃|2
 😄|3
 😁|4
 😆|5
 😅|6
-😂|7
-🤣|8
-☺️|9
-😊|10
-😇|11
-🙂|12
-🙃|13
-😉|14
-😌|15
-🥲|16
-😍|17
-🥰|18
-😘|19
-😗|20
-😙|21
-😚|22
-😋|23
-😛|24
-😝|25
+😂|8
+🤣|7
+☺️|20
+😊|13
+😇|14
+🙂|9
+🙃|10
+😉|12
+😌|50
+🥲|23
+😍|16
+🥰|15
+😘|18
+😗|19
+😙|22
+😚|21
+😋|24
+😛|25
+😝|28
 😜|26
 🤪|27
-🤨|28
-🧐|29
-🤓|30
-😎|31
-🤩|32
-🥳|33
-😏|34
-😒|35
-😞|36
-😔|37
-😟|38
-😕|39
-🙁|40
-😣|42
-😖|43
-😫|44
-😩|45
-🥺|46
-😢|47
-😭|48
-😤|49
-😮‍💨|50
-😠|51
-😡|52
-🤬|53
-🤯|54
-😳|55
-😶‍🌫️|56
-🥵|57
-🥶|58
-😱|59
-😨|60
-😰|61
-😥|62
-😓|63
-🤗|64
-🤔|65
-🤭|66
-🥱|67
-🤫|68
-🤥|69
-😶|70
-😐|71
-😑|72
-😬|73
-🙄|74
-😯|75
-😦|76
-😧|77
+🤨|38
+🧐|72
+🤓|71
+😎|70
+🤩|17
+🥳|68
+😏|44
+😒|45
+😞|94
+😔|51
+😟|75
+😕|73
+🙁|76
+😣|93
+😖|92
+😫|97
+😩|96
+🥺|82
+😢|89
+😭|90
+😤|99
+😮‍💨|48
+😠|101
+😡|100
+🤬|102
+🤯|66
+😳|81
+😶‍🌫️|43
+🥵|61
+🥶|62
+😱|91
+😨|86
+😰|87
+😥|88
+😓|95
+🤗|30
+🤔|35
+🤭|31
+🥱|98
+🤫|34
+🤥|49
+😶|41
+😐|39
+😑|40
+😬|47
+🙄|46
+😯|79
+😦|84
+😧|85
 😮|78
-😲|79
-😴|80
-🤤|81
-😪|82
-😵|83
-😵‍💫|84
-🤐|85
-🥴|86
-🤢|87
-🤮|88
-🤧|89
-😷|90
-🤒|91
-🤕|92
-🤑|93
-🤠|94
-🥸|95
-😈|96
-👿|97
-👹|98
-👺|99
-🤡|100
-💩|101
-👻|102
-💀|103
-👽|105
-👾|106
-🤖|107
-🎃|108
-😺|109
-😸|110
-😹|111
-😻|112
-😼|113
-😽|114
-🙀|115
-😿|116
-😾|117
-🤲|118
-🤲🏻|119
-🤲🏼|120
-🤲🏽|121
-🤲🏾|122
-🤲🏿|123
-👐|124
-👐🏻|125
-👐🏼|126
-👐🏽|127
-👐🏾|128
-👐🏿|129
-🙌|130
-🙌🏻|131
-🙌🏼|132
-🙌🏽|133
-🙌🏾|134
-🙌🏿|135
-👏|136
-👏🏻|137
-👏🏼|138
-👏🏽|139
-👏🏾|140
-👏🏿|141
-🤝|142
-👍|143
-👍🏻|144
-👍🏼|145
-👍🏽|146
-👍🏾|147
-👍🏿|148
-👎|149
-👎🏻|150
-👎🏼|151
-👎🏽|152
-👎🏾|153
-👎🏿|154
-👊|155
-👊🏻|156
-👊🏼|157
-👊🏽|158
-👊🏾|159
-👊🏿|160
-✊|161
-✊🏻|162
-✊🏼|163
-✊🏽|164
-✊🏾|165
-✊🏿|166
-🤛|167
-🤛🏻|168
-🤛🏼|169
-🤛🏽|170
-🤛🏾|171
-🤛🏿|172
-🤜|173
-🤜🏻|174
-🤜🏼|175
-🤜🏽|176
-🤜🏾|177
-🤜🏿|178
-🤞|179
-🤞🏻|180
-🤞🏼|181
-🤞🏽|182
-🤞🏾|183
-🤞🏿|184
-✌️|185
-✌🏻|186
-✌🏼|187
-✌🏽|188
-✌🏾|189
-✌🏿|190
-🤟|191
-🤟🏻|192
-🤟🏼|193
-🤟🏽|194
-🤟🏾|195
-🤟🏿|196
-🤘|197
-🤘🏻|198
-🤘🏼|199
-🤘🏽|200
-🤘🏾|201
-🤘🏿|202
-👌|203
-👌🏻|204
-👌🏼|205
-👌🏽|206
-👌🏾|207
-👌🏿|208
-🤏|209
-🤏🏻|210
-🤏🏼|211
-🤏🏽|212
-🤏🏾|213
-🤏🏿|214
-🤌|215
-🤌🏼|216
-🤌🏻|217
-🤌🏽|218
-🤌🏾|219
-🤌🏿|220
-👈|221
-👈🏻|222
-👈🏼|223
-👈🏽|224
-👈🏾|225
-👈🏿|226
-👉|227
-👉🏻|228
-👉🏼|229
-👉🏽|230
-👉🏾|231
-👉🏿|232
-👆|233
-👆🏻|234
-👆🏼|235
-👆🏽|236
-👆🏾|237
-👆🏿|238
-👇|239
-👇🏻|240
-👇🏼|241
-👇🏽|242
-👇🏾|243
-👇🏿|244
-☝️|245
-☝🏻|246
-☝🏼|247
-☝🏽|248
-☝🏾|249
-☝🏿|250
-✋|251
-✋🏻|252
-✋🏼|253
-✋🏽|254
-✋🏾|255
-✋🏿|256
-🤚|257
-🤚🏻|258
-🤚🏼|259
-🤚🏽|260
-🤚🏾|261
-🤚🏿|262
-🖐|263
-🖐🏻|264
-🖐🏼|265
-🖐🏽|266
-🖐🏾|267
-🖐🏿|268
-🖖|269
-🖖🏻|270
-🖖🏼|271
-🖖🏽|272
-🖖🏾|273
-🖖🏿|274
-👋|275
-👋🏻|276
-👋🏼|277
-👋🏽|278
-👋🏾|279
-👋🏿|280
-🤙|281
-🤙🏻|282
-🤙🏼|283
-🤙🏽|284
-🤙🏾|285
-🤙🏿|286
-💪|287
-💪🏻|288
-💪🏼|289
-💪🏽|290
-💪🏾|291
-💪🏿|292
-🦾|293
-🖕|294
-🖕🏻|295
-🖕🏼|296
-🖕🏽|297
-🖕🏾|298
-🖕🏿|299
-✍️|300
-✍🏻|301
-✍🏼|302
-✍🏽|303
-✍🏾|304
-✍🏿|305
-🙏|306
-🙏🏻|307
-🙏🏼|308
-🙏🏽|309
-🙏🏾|310
-🙏🏿|311
-🦶|312
-🦶🏻|313
-🦶🏼|314
-🦶🏽|315
-🦶🏾|316
-🦶🏿|317
-🦵|318
-🦵🏻|319
-🦵🏼|320
-🦵🏽|321
-🦵🏾|322
-🦵🏿|323
-🦿|324
-💄|325
-💋|326
-👄|327
-🦷|328
-👅|329
-👂|330
-👂🏻|331
-👂🏼|332
-👂🏽|333
-👂🏾|334
-👂🏿|335
-🦻|336
-🦻🏻|337
-🦻🏼|338
-🦻🏽|339
-🦻🏾|340
-🦻🏿|341
-👃|342
-👃🏻|343
-👃🏼|344
-👃🏽|345
-👃🏾|346
-👃🏿|347
-👣|348
-👁️|349
-👀|350
-🧠|351
-🫀|352
-🫁|353
-🦴|354
-🗣️|355
-👤|356
-👥|357
-🫂|358
-👶|359
-👶🏻|360
-👶🏼|361
-👶🏽|362
-👶🏾|363
-👶🏿|364
-👧|365
-👧🏻|366
-👧🏼|367
-👧🏽|368
-👧🏾|369
-👧🏿|370
-🧒|371
-🧒🏻|372
-🧒🏼|373
-🧒🏽|374
-🧒🏾|375
-🧒🏿|376
-👦|377
-👦🏻|378
-👦🏼|379
-👦🏽|380
-👦🏾|381
-👦🏿|382
-👩|383
-👩🏻|384
-👩🏼|385
-👩🏽|386
-👩🏾|387
-👩🏿|388
-🧑|389
-🧑🏻|390
-🧑🏼|391
-🧑🏽|392
-🧑🏾|393
-🧑🏿|394
-👨|395
-👨🏻|396
-👨🏼|397
-👨🏽|398
-👨🏾|399
-👨🏿|400
-🧑‍🦱|401
-🧑🏻‍🦱|402
-🧑🏼‍🦱|403
-🧑🏽‍🦱|404
-🧑🏾‍🦱|405
-🧑🏿‍🦱|406
-👩‍🦱|407
-👩🏻‍🦱|408
-👩🏼‍🦱|409
-👩🏽‍🦱|410
-👩🏾‍🦱|411
-👩🏿‍🦱|412
-👨‍🦱|413
-👨🏻‍🦱|414
-👨🏼‍🦱|415
-👨🏽‍🦱|416
-👨🏾‍🦱|417
-👨🏿‍🦱|418
-🧑‍🦰|419
-🧑🏻‍🦰|420
-🧑🏼‍🦰|421
-🧑🏽‍🦰|422
-🧑🏾‍🦰|423
-🧑🏿‍🦰|424
-👩‍🦰|425
-👩🏻‍🦰|426
-👩🏼‍🦰|427
-👩🏽‍🦰|428
-👩🏾‍🦰|429
-👩🏿‍🦰|430
-👨‍🦰|431
-👨🏻‍🦰|432
-👨🏼‍🦰|433
-👨🏽‍🦰|434
-👨🏾‍🦰|435
-👨🏿‍🦰|436
-👱‍♀️|437
-👱🏻‍♀️|438
-👱🏼‍♀️|439
-👱🏽‍♀️|440
-👱🏾‍♀️|441
-👱🏿‍♀️|442
-👱|443
-👱🏻|444
-👱🏼|445
-👱🏽|446
-👱🏾|447
-👱🏿|448
-👱‍♂️|449
-👱🏻‍♂️|450
-👱🏼‍♂️|451
-👱🏽‍♂️|452
-👱🏾‍♂️|453
-👱🏿‍♂️|454
-🧑‍🦳|455
-🧑🏻‍🦳|456
-🧑🏼‍🦳|457
-🧑🏽‍🦳|458
-🧑🏾‍🦳|459
-🧑🏿‍🦳|460
-👩‍🦳|461
-👩🏻‍🦳|462
-👩🏼‍🦳|463
-👩🏽‍🦳|464
-👩🏾‍🦳|465
-👩🏿‍🦳|466
-👨‍🦳|467
-👨🏻‍🦳|468
-👨🏼‍🦳|469
-👨🏽‍🦳|470
-👨🏾‍🦳|471
-👨🏿‍🦳|472
-🧑‍🦲|473
-🧑🏻‍🦲|474
-🧑🏼‍🦲|475
-🧑🏽‍🦲|476
-🧑🏾‍🦲|477
-🧑🏿‍🦲|478
-👩‍🦲|479
-👩🏻‍🦲|480
-👩🏼‍🦲|481
-👩🏽‍🦲|482
-👩🏾‍🦲|483
-👩🏿‍🦲|484
-👨‍🦲|485
-👨🏻‍🦲|486
-👨🏼‍🦲|487
-👨🏽‍🦲|488
-👨🏾‍🦲|489
-👨🏿‍🦲|490
-🧔|491
-🧔🏻|492
-🧔🏼|493
-🧔🏽|494
-🧔🏾|495
-🧔🏿|496
-🧔‍♂️|497
-🧔🏻‍♂️|498
-🧔🏼‍♂️|499
-🧔🏽‍♂️|500
-🧔🏾‍♂️|501
-🧔🏿‍♂️|502
-🧔‍♀️|503
-🧔🏻‍♀️|504
-🧔🏼‍♀️|505
-🧔🏽‍♀️|506
-🧔🏾‍♀️|507
-🧔🏿‍♀️|508
-👵|509
-👵🏻|510
-👵🏼|511
-👵🏽|512
-👵🏾|513
-👵🏿|514
-🧓|515
-🧓🏻|516
-🧓🏼|517
-🧓🏽|518
-🧓🏾|519
-🧓🏿|520
-👴|521
-👴🏻|522
-👴🏼|523
-👴🏽|524
-👴🏾|525
-👴🏿|526
-👲|527
-👲🏻|528
-👲🏼|529
-👲🏽|530
-👲🏾|531
-👲🏿|532
-👳|533
-👳🏻|534
-👳🏼|535
-👳🏽|536
-👳🏾|537
-👳🏿|538
-👳‍♀️|539
-👳🏻‍♀️|540
-👳🏼‍♀️|541
-👳🏽‍♀️|542
-👳🏾‍♀️|543
-👳🏿‍♀️|544
-👳‍♂️|545
-👳🏻‍♂️|546
-👳🏼‍♂️|547
-👳🏽‍♂️|548
-👳🏾‍♂️|549
-👳🏿‍♂️|550
-🧕|551
-🧕🏻|552
-🧕🏼|553
-🧕🏽|554
-🧕🏾|555
-🧕🏿|556
-👮|557
-👮🏻|558
-👮🏼|559
-👮🏽|560
-👮🏾|561
-👮🏿|562
-👮‍♀️|563
-👮🏻‍♀️|564
-👮🏼‍♀️|565
-👮🏽‍♀️|566
-👮🏾‍♀️|567
-👮🏿‍♀️|568
-👮‍♂️|569
-👮🏻‍♂️|570
-👮🏼‍♂️|571
-👮🏽‍♂️|572
-👮🏾‍♂️|573
-👮🏿‍♂️|574
-👷|575
-👷🏻|576
-👷🏼|577
-👷🏽|578
-👷🏾|579
-👷🏿|580
-👷‍♀️|581
-👷🏻‍♀️|582
-👷🏼‍♀️|583
-👷🏽‍♀️|584
-👷🏾‍♀️|585
-👷🏿‍♀️|586
-👷‍♂️|587
-👷🏻‍♂️|588
-👷🏼‍♂️|589
-👷🏽‍♂️|590
-👷🏾‍♂️|591
-👷🏿‍♂️|592
-💂|593
-💂🏻|594
-💂🏼|595
-💂🏽|596
-💂🏾|597
-💂🏿|598
-💂‍♀️|599
-💂🏻‍♀️|600
-💂🏼‍♀️|601
-💂🏽‍♀️|602
-💂🏾‍♀️|603
-💂🏿‍♀️|604
-💂‍♂️|605
-💂🏻‍♂️|606
-💂🏼‍♂️|607
-💂🏽‍♂️|608
-💂🏾‍♂️|609
-💂🏿‍♂️|610
-🕵|611
-🕵🏻|612
-🕵🏼|613
-🕵🏽|614
-🕵🏾|615
-🕵🏿|616
-🕵️‍♀️|617
-🕵🏻‍♀️|618
-🕵🏼‍♀️|619
-🕵🏽‍♀️|620
-🕵🏾‍♀️|621
-🕵🏿‍♀️|622
-🕵️‍♂️|623
-🕵🏻‍♂️|624
-🕵🏼‍♂️|625
-🕵🏽‍♂️|626
-🕵🏾‍♂️|627
-🕵🏿‍♂️|628
-🧑‍⚕️|629
-🧑🏻‍⚕️|630
-🧑🏼‍⚕️|631
-🧑🏽‍⚕️|632
-🧑🏾‍⚕️|633
-🧑🏿‍⚕️|634
-👩‍⚕️|635
-👩🏻‍⚕️|636
-👩🏼‍⚕️|637
-👩🏽‍⚕️|638
-👩🏾‍⚕️|639
-👩🏿‍⚕️|640
-👨‍⚕️|641
-👨🏻‍⚕️|642
-👨🏼‍⚕️|643
-👨🏽‍⚕️|644
-👨🏾‍⚕️|645
-👨🏿‍⚕️|646
-🧑‍🌾|647
-🧑🏻‍🌾|648
-🧑🏼‍🌾|649
-🧑🏽‍🌾|650
-🧑🏾‍🌾|651
-🧑🏿‍🌾|652
-👩‍🌾|653
-👩🏻‍🌾|654
-👩🏼‍🌾|655
-👩🏽‍🌾|656
-👩🏾‍🌾|657
-👩🏿‍🌾|658
-👨‍🌾|659
-👨🏻‍🌾|660
-👨🏼‍🌾|661
-👨🏽‍🌾|662
-👨🏾‍🌾|663
-👨🏿‍🌾|664
-🧑‍🍳|665
-🧑🏻‍🍳|666
-🧑🏼‍🍳|667
-🧑🏽‍🍳|668
-🧑🏾‍🍳|669
-🧑🏿‍🍳|670
-👩‍🍳|671
-👩🏻‍🍳|672
-👩🏼‍🍳|673
-👩🏽‍🍳|674
-👩🏾‍🍳|675
-👩🏿‍🍳|676
-👨‍🍳|677
-👨🏻‍🍳|678
-👨🏼‍🍳|679
-👨🏽‍🍳|680
-👨🏾‍🍳|681
-👨🏿‍🍳|682
-🧑‍🎓|683
-🧑🏻‍🎓|684
-🧑🏼‍🎓|685
-🧑🏽‍🎓|686
-🧑🏾‍🎓|687
-🧑🏿‍🎓|688
-👩‍🎓|689
-👩🏻‍🎓|690
-👩🏼‍🎓|691
-👩🏽‍🎓|692
-👩🏾‍🎓|693
-👩🏿‍🎓|694
-👨‍🎓|695
-👨🏻‍🎓|696
-👨🏼‍🎓|697
-👨🏽‍🎓|698
-👨🏾‍🎓|699
-👨🏿‍🎓|700
-🧑‍🎤|701
-🧑🏻‍🎤|702
-🧑🏼‍🎤|703
-🧑🏽‍🎤|704
-🧑🏾‍🎤|705
-🧑🏿‍🎤|706
-👩‍🎤|707
-👩🏻‍🎤|708
-👩🏼‍🎤|709
-👩🏽‍🎤|710
-👩🏾‍🎤|711
-👩🏿‍🎤|712
-👨‍🎤|713
-👨🏻‍🎤|714
-👨🏼‍🎤|715
-👨🏽‍🎤|716
-👨🏾‍🎤|717
-👨🏿‍🎤|718
-🧑‍🏫|719
-🧑🏻‍🏫|720
-🧑🏼‍🏫|721
-🧑🏽‍🏫|722
-🧑🏾‍🏫|723
-🧑🏿‍🏫|724
-👩‍🏫|725
-👩🏻‍🏫|726
-👩🏼‍🏫|727
-👩🏽‍🏫|728
-👩🏾‍🏫|729
-👩🏿‍🏫|730
-👨‍🏫|731
-👨🏻‍🏫|732
-👨🏼‍🏫|733
-👨🏽‍🏫|734
-👨🏾‍🏫|735
-👨🏿‍🏫|736
-🧑‍🏭|737
-🧑🏻‍🏭|738
-🧑🏼‍🏭|739
-🧑🏽‍🏭|740
-🧑🏾‍🏭|741
-🧑🏿‍🏭|742
-👩‍🏭|743
-👩🏻‍🏭|744
-👩🏼‍🏭|745
-👩🏽‍🏭|746
-👩🏾‍🏭|747
-👩🏿‍🏭|748
-👨‍🏭|749
-👨🏻‍🏭|750
-👨🏼‍🏭|751
-👨🏽‍🏭|752
-👨🏾‍🏭|753
-👨🏿‍🏭|754
-🧑‍💻|755
-🧑🏻‍💻|756
-🧑🏼‍💻|757
-🧑🏽‍💻|758
-🧑🏾‍💻|759
-🧑🏿‍💻|760
-👩‍💻|761
-👩🏻‍💻|762
-👩🏼‍💻|763
-👩🏽‍💻|764
-👩🏾‍💻|765
-👩🏿‍💻|766
-👨‍💻|767
-👨🏻‍💻|768
-👨🏼‍💻|769
-👨🏽‍💻|770
-👨🏾‍💻|771
-👨🏿‍💻|772
-🧑‍💼|773
-🧑🏻‍💼|774
-🧑🏼‍💼|775
-🧑🏽‍💼|776
-🧑🏾‍💼|777
-🧑🏿‍💼|778
-👩‍💼|779
-👩🏻‍💼|780
-👩🏼‍💼|781
-👩🏽‍💼|782
-👩🏾‍💼|783
-👩🏿‍💼|784
-👨‍💼|785
-👨🏻‍💼|786
-👨🏼‍💼|787
-👨🏽‍💼|788
-👨🏾‍💼|789
-👨🏿‍💼|790
-🧑‍🔧|791
-🧑🏻‍🔧|792
-🧑🏼‍🔧|793
-🧑🏽‍🔧|794
-🧑🏾‍🔧|795
-🧑🏿‍🔧|796
-👩‍🔧|797
-👩🏻‍🔧|798
-👩🏼‍🔧|799
-👩🏽‍🔧|800
-👩🏾‍🔧|801
-👩🏿‍🔧|802
-👨‍🔧|803
-👨🏻‍🔧|804
-👨🏼‍🔧|805
-👨🏽‍🔧|806
-👨🏾‍🔧|807
-👨🏿‍🔧|808
-🧑‍🔬|809
-🧑🏻‍🔬|810
-🧑🏼‍🔬|811
-🧑🏽‍🔬|812
-🧑🏾‍🔬|813
-🧑🏿‍🔬|814
-👩‍🔬|815
-👩🏻‍🔬|816
-👩🏼‍🔬|817
-👩🏽‍🔬|818
-👩🏾‍🔬|819
-👩🏿‍🔬|820
-👨‍🔬|821
-👨🏻‍🔬|822
-👨🏼‍🔬|823
-👨🏽‍🔬|824
-👨🏾‍🔬|825
-👨🏿‍🔬|826
-🧑‍🎨|827
-🧑🏻‍🎨|828
-🧑🏼‍🎨|829
-🧑🏽‍🎨|830
-🧑🏾‍🎨|831
-🧑🏿‍🎨|832
-👩‍🎨|833
-👩🏻‍🎨|834
-👩🏼‍🎨|835
-👩🏽‍🎨|836
-👩🏾‍🎨|837
-👩🏿‍🎨|838
-👨‍🎨|839
-👨🏻‍🎨|840
-👨🏼‍🎨|841
-👨🏽‍🎨|842
-👨🏾‍🎨|843
-👨🏿‍🎨|844
-🧑‍🚒|845
-🧑🏻‍🚒|846
-🧑🏼‍🚒|847
-🧑🏽‍🚒|848
-🧑🏾‍🚒|849
-🧑🏿‍🚒|850
-👩‍🚒|851
-👩🏻‍🚒|852
-👩🏼‍🚒|853
-👩🏽‍🚒|854
-👩🏾‍🚒|855
-👩🏿‍🚒|856
-👨‍🚒|857
-👨🏻‍🚒|858
-👨🏼‍🚒|859
-👨🏽‍🚒|860
-👨🏾‍🚒|861
-👨🏿‍🚒|862
-🧑‍✈️|863
-🧑🏻‍✈️|864
-🧑🏼‍✈️|865
-🧑🏽‍✈️|866
-🧑🏾‍✈️|867
-🧑🏿‍✈️|868
-👩‍✈️|869
-👩🏻‍✈️|870
-👩🏼‍✈️|871
-👩🏽‍✈️|872
-👩🏾‍✈️|873
-👩🏿‍✈️|874
-👨‍✈️|875
-👨🏻‍✈️|876
-👨🏼‍✈️|877
-👨🏽‍✈️|878
-👨🏾‍✈️|879
-👨🏿‍✈️|880
-🧑‍🚀|881
-🧑🏻‍🚀|882
-🧑🏼‍🚀|883
-🧑🏽‍🚀|884
-🧑🏾‍🚀|885
-🧑🏿‍🚀|886
-👩‍🚀|887
-👩🏻‍🚀|888
-👩🏼‍🚀|889
-👩🏽‍🚀|890
-👩🏾‍🚀|891
-👩🏿‍🚀|892
-👨‍🚀|893
-👨🏻‍🚀|894
-👨🏼‍🚀|895
-👨🏽‍🚀|896
-👨🏾‍🚀|897
-👨🏿‍🚀|898
-🧑‍⚖️|899
-🧑🏻‍⚖️|900
-🧑🏼‍⚖️|901
-🧑🏽‍⚖️|902
-🧑🏾‍⚖️|903
-🧑🏿‍⚖️|904
-👩‍⚖️|905
-👩🏻‍⚖️|906
-👩🏼‍⚖️|907
-👩🏽‍⚖️|908
-👩🏾‍⚖️|909
-👩🏿‍⚖️|910
-👨‍⚖️|911
-👨🏻‍⚖️|912
-👨🏼‍⚖️|913
-👨🏽‍⚖️|914
-👨🏾‍⚖️|915
-👨🏿‍⚖️|916
-👰|917
-👰🏻|918
-👰🏼|919
-👰🏽|920
-👰🏾|921
-👰🏿|922
-👰‍♀️|923
-👰🏻‍♀️|924
-👰🏼‍♀️|925
-👰🏽‍♀️|926
-👰🏾‍♀️|927
-👰🏿‍♀️|928
-👰‍♂️|929
-👰🏻‍♂️|930
-👰🏼‍♂️|931
-👰🏽‍♂️|932
-👰🏾‍♂️|933
-👰🏿‍♂️|934
-🤵|935
-🤵🏻|936
-🤵🏼|937
-🤵🏽|938
-🤵🏾|939
-🤵🏿|940
-🤵‍♀️|941
-🤵🏻‍♀️|942
-🤵🏼‍♀️|943
-🤵🏽‍♀️|944
-🤵🏾‍♀️|945
-🤵🏿‍♀️|946
-🤵‍♂️|947
-🤵🏻‍♂️|948
-🤵🏼‍♂️|949
-🤵🏽‍♂️|950
-🤵🏾‍♂️|951
-🤵🏿‍♂️|952
-👸|953
-👸🏻|954
-👸🏼|955
-👸🏽|956
-👸🏾|957
-👸🏿|958
-🤴|959
-🤴🏻|960
-🤴🏼|961
-🤴🏽|962
-🤴🏾|963
-🤴🏿|964
-🦸|965
-🦸🏻|966
-🦸🏼|967
-🦸🏽|968
-🦸🏾|969
-🦸🏿|970
-🦸‍♀️|971
-🦸🏻‍♀️|972
-🦸🏼‍♀️|973
-🦸🏽‍♀️|974
-🦸🏾‍♀️|975
-🦸🏿‍♀️|976
-🦸‍♂️|977
-🦸🏻‍♂️|978
-🦸🏼‍♂️|979
-🦸🏽‍♂️|980
-🦸🏾‍♂️|981
-🦸🏿‍♂️|982
-🦹|983
-🦹🏻|984
-🦹🏼|985
-🦹🏽|986
-🦹🏾|987
-🦹🏿|988
-🦹‍♀️|989
-🦹🏻‍♀️|990
-🦹🏼‍♀️|991
-🦹🏽‍♀️|992
-🦹🏾‍♀️|993
-🦹🏿‍♀️|994
-🦹‍♂️|995
-🦹🏻‍♂️|996
-🦹🏼‍♂️|997
-🦹🏽‍♂️|998
-🦹🏾‍♂️|999
-🦹🏿‍♂️|1000
-🥷|1001
-🥷🏻|1002
-🥷🏼|1003
-🥷🏽|1004
-🥷🏾|1005
-🥷🏿|1006
-🧑‍🎄|1007
-🧑🏻‍🎄|1008
-🧑🏼‍🎄|1009
-🧑🏽‍🎄|1010
-🧑🏾‍🎄|1011
-🧑🏿‍🎄|1012
-🤶|1013
-🤶🏻|1014
-🤶🏼|1015
-🤶🏽|1016
-🤶🏾|1017
-🤶🏿|1018
-🎅|1019
-🎅🏻|1020
-🎅🏼|1021
-🎅🏽|1022
-🎅🏾|1023
-🎅🏿|1024
-🧙|1025
-🧙🏻|1026
-🧙🏼|1027
-🧙🏽|1028
-🧙🏾|1029
-🧙🏿|1030
-🧙‍♀️|1031
-🧙🏻‍♀️|1032
-🧙🏼‍♀️|1033
-🧙🏽‍♀️|1034
-🧙🏾‍♀️|1035
-🧙🏿‍♀️|1036
-🧙‍♂️|1037
-🧙🏻‍♂️|1038
-🧙🏼‍♂️|1039
-🧙🏽‍♂️|1040
-🧙🏾‍♂️|1041
-🧙🏿‍♂️|1042
-🧝|1043
-🧝🏻|1044
-🧝🏼|1045
-🧝🏽|1046
-🧝🏾|1047
-🧝🏿|1048
-🧝‍♀️|1049
-🧝🏻‍♀️|1050
-🧝🏼‍♀️|1051
-🧝🏽‍♀️|1052
-🧝🏾‍♀️|1053
-🧝🏿‍♀️|1054
-🧝‍♂️|1055
-🧝🏻‍♂️|1056
-🧝🏼‍♂️|1057
-🧝🏽‍♂️|1058
-🧝🏾‍♂️|1059
-🧝🏿‍♂️|1060
-🧛|1061
-🧛🏻|1062
-🧛🏼|1063
-🧛🏽|1064
-🧛🏾|1065
-🧛🏿|1066
-🧛‍♀️|1067
-🧛🏻‍♀️|1068
-🧛🏼‍♀️|1069
-🧛🏽‍♀️|1070
-🧛🏾‍♀️|1071
-🧛🏿‍♀️|1072
-🧛‍♂️|1073
-🧛🏻‍♂️|1074
-🧛🏼‍♂️|1075
-🧛🏽‍♂️|1076
-🧛🏾‍♂️|1077
-🧛🏿‍♂️|1078
-🧟|1079
-🧟‍♀️|1080
-🧟‍♂️|1081
-🧞|1082
-🧞‍♀️|1083
-🧞‍♂️|1084
-🧜|1085
-🧜🏻|1086
-🧜🏼|1087
-🧜🏽|1088
-🧜🏾|1089
-🧜🏿|1090
-🧜‍♀️|1091
-🧜🏻‍♀️|1092
-🧜🏼‍♀️|1093
-🧜🏽‍♀️|1094
-🧜🏾‍♀️|1095
-🧜🏿‍♀️|1096
-🧜‍♂️|1097
-🧜🏻‍♂️|1098
-🧜🏼‍♂️|1099
-🧜🏽‍♂️|1100
-🧜🏾‍♂️|1101
-🧜🏿‍♂️|1102
-🧚|1103
-🧚🏻|1104
-🧚🏼|1105
-🧚🏽|1106
-🧚🏾|1107
-🧚🏿|1108
-🧚‍♀️|1109
-🧚🏻‍♀️|1110
-🧚🏼‍♀️|1111
-🧚🏽‍♀️|1112
-🧚🏾‍♀️|1113
-🧚🏿‍♀️|1114
-🧚‍♂️|1115
-🧚🏻‍♂️|1116
-🧚🏼‍♂️|1117
-🧚🏽‍♂️|1118
-🧚🏾‍♂️|1119
-🧚🏿‍♂️|1120
-👼|1121
-👼🏻|1122
-👼🏼|1123
-👼🏽|1124
-👼🏾|1125
-👼🏿|1126
-🤰|1127
-🤰🏻|1128
-🤰🏼|1129
-🤰🏽|1130
-🤰🏾|1131
-🤰🏿|1132
-🤱|1133
-🤱🏻|1134
-🤱🏼|1135
-🤱🏽|1136
-🤱🏾|1137
-🤱🏿|1138
-🧑‍🍼|1139
-🧑🏻‍🍼|1140
-🧑🏼‍🍼|1141
-🧑🏽‍🍼|1142
-🧑🏾‍🍼|1143
-🧑🏿‍🍼|1144
-👩‍🍼|1145
-👩🏻‍🍼|1146
-👩🏼‍🍼|1147
-👩🏽‍🍼|1148
-👩🏾‍🍼|1149
-👩🏿‍🍼|1150
-👨‍🍼|1151
-👨🏻‍🍼|1152
-👨🏼‍🍼|1153
-👨🏽‍🍼|1154
-👨🏾‍🍼|1155
-👨🏿‍🍼|1156
-🙇|1157
-🙇🏻|1158
-🙇🏼|1159
-🙇🏽|1160
-🙇🏾|1161
-🙇🏿|1162
-🙇‍♀️|1163
-🙇🏻‍♀️|1164
-🙇🏼‍♀️|1165
-🙇🏽‍♀️|1166
-🙇🏾‍♀️|1167
-🙇🏿‍♀️|1168
-🙇‍♂️|1169
-🙇🏻‍♂️|1170
-🙇🏼‍♂️|1171
-🙇🏽‍♂️|1172
-🙇🏾‍♂️|1173
-🙇🏿‍♂️|1174
-💁|1175
-💁🏻|1176
-💁🏼|1177
-💁🏽|1178
-💁🏾|1179
-💁🏿|1180
-💁‍♀️|1181
-💁🏻‍♀️|1182
-💁🏼‍♀️|1183
-💁🏽‍♀️|1184
-💁🏾‍♀️|1185
-💁🏿‍♀️|1186
-💁‍♂️|1187
-💁🏻‍♂️|1188
-💁🏼‍♂️|1189
-💁🏽‍♂️|1190
-💁🏾‍♂️|1191
-💁🏿‍♂️|1192
-🙅|1193
-🙅🏻|1194
-🙅🏼|1195
-🙅🏽|1196
-🙅🏾|1197
-🙅🏿|1198
-🙅‍♀️|1199
-🙅🏻‍♀️|1200
-🙅🏼‍♀️|1201
-🙅🏽‍♀️|1202
-🙅🏾‍♀️|1203
-🙅🏿‍♀️|1204
-🙅‍♂️|1205
-🙅🏻‍♂️|1206
-🙅🏼‍♂️|1207
-🙅🏽‍♂️|1208
-🙅🏾‍♂️|1209
-🙅🏿‍♂️|1210
-🙆|1211
-🙆🏻|1212
-🙆🏼|1213
-🙆🏽|1214
-🙆🏾|1215
-🙆🏿|1216
-🙆‍♀️|1217
-🙆🏻‍♀️|1218
-🙆🏼‍♀️|1219
-🙆🏽‍♀️|1220
-🙆🏾‍♀️|1221
-🙆🏿‍♀️|1222
-🙆‍♂️|1223
-🙆🏻‍♂️|1224
-🙆🏼‍♂️|1225
-🙆🏽‍♂️|1226
-🙆🏾‍♂️|1227
-🙆🏿‍♂️|1228
-🙋|1229
-🙋🏻|1230
-🙋🏼|1231
-🙋🏽|1232
-🙋🏾|1233
-🙋🏿|1234
-🙋‍♀️|1235
-🙋🏻‍♀️|1236
-🙋🏼‍♀️|1237
-🙋🏽‍♀️|1238
-🙋🏾‍♀️|1239
-🙋🏿‍♀️|1240
-🙋‍♂️|1241
-🙋🏻‍♂️|1242
-🙋🏼‍♂️|1243
-🙋🏽‍♂️|1244
-🙋🏾‍♂️|1245
-🙋🏿‍♂️|1246
-🧏|1247
-🧏🏻|1248
-🧏🏼|1249
-🧏🏽|1250
-🧏🏾|1251
-🧏🏿|1252
-🧏‍♀️|1253
-🧏🏻‍♀️|1254
-🧏🏼‍♀️|1255
-🧏🏽‍♀️|1256
-🧏🏾‍♀️|1257
-🧏🏿‍♀️|1258
-🧏‍♂️|1259
-🧏🏻‍♂️|1260
-🧏🏼‍♂️|1261
-🧏🏽‍♂️|1262
-🧏🏾‍♂️|1263
-🧏🏿‍♂️|1264
-🤦|1265
-🤦🏻|1266
-🤦🏼|1267
-🤦🏽|1268
-🤦🏾|1269
-🤦🏿|1270
-🤦‍♀️|1271
-🤦🏻‍♀️|1272
-🤦🏼‍♀️|1273
-🤦🏽‍♀️|1274
-🤦🏾‍♀️|1275
-🤦🏿‍♀️|1276
-🤦‍♂️|1277
-🤦🏻‍♂️|1278
-🤦🏼‍♂️|1279
-🤦🏽‍♂️|1280
-🤦🏾‍♂️|1281
-🤦🏿‍♂️|1282
-🤷|1283
-🤷🏻|1284
-🤷🏼|1285
-🤷🏽|1286
-🤷🏾|1287
-🤷🏿|1288
-🤷‍♀️|1289
-🤷🏻‍♀️|1290
-🤷🏼‍♀️|1291
-🤷🏽‍♀️|1292
-🤷🏾‍♀️|1293
-🤷🏿‍♀️|1294
-🤷‍♂️|1295
-🤷🏻‍♂️|1296
-🤷🏼‍♂️|1297
-🤷🏽‍♂️|1298
-🤷🏾‍♂️|1299
-🤷🏿‍♂️|1300
-🙎|1301
-🙎🏻|1302
-🙎🏼|1303
-🙎🏽|1304
-🙎🏾|1305
-🙎🏿|1306
-🙎‍♀️|1307
-🙎🏻‍♀️|1308
-🙎🏼‍♀️|1309
-🙎🏽‍♀️|1310
-🙎🏾‍♀️|1311
-🙎🏿‍♀️|1312
-🙎‍♂️|1313
-🙎🏻‍♂️|1314
-🙎🏼‍♂️|1315
-🙎🏽‍♂️|1316
-🙎🏾‍♂️|1317
-🙎🏿‍♂️|1318
-🙍|1319
-🙍🏻|1320
-🙍🏼|1321
-🙍🏽|1322
-🙍🏾|1323
-🙍🏿|1324
-🙍‍♀️|1325
-🙍🏻‍♀️|1326
-🙍🏼‍♀️|1327
-🙍🏽‍♀️|1328
-🙍🏾‍♀️|1329
-🙍🏿‍♀️|1330
-🙍‍♂️|1331
-🙍🏻‍♂️|1332
-🙍🏼‍♂️|1333
-🙍🏽‍♂️|1334
-🙍🏾‍♂️|1335
-🙍🏿‍♂️|1336
-💇|1337
-💇🏻|1338
-💇🏼|1339
-💇🏽|1340
-💇🏾|1341
-💇🏿|1342
-💇‍♀️|1343
-💇🏻‍♀️|1344
-💇🏼‍♀️|1345
-💇🏽‍♀️|1346
-💇🏾‍♀️|1347
-💇🏿‍♀️|1348
-💇‍♂️|1349
-💇🏻‍♂️|1350
-💇🏼‍♂️|1351
-💇🏽‍♂️|1352
-💇🏾‍♂️|1353
-💇🏿‍♂️|1354
-💆|1355
-💆🏻|1356
-💆🏼|1357
-💆🏽|1358
-💆🏾|1359
-💆🏿|1360
-💆‍♀️|1361
-💆🏻‍♀️|1362
-💆🏼‍♀️|1363
-💆🏽‍♀️|1364
-💆🏾‍♀️|1365
-💆🏿‍♀️|1366
-💆‍♂️|1367
-💆🏻‍♂️|1368
-💆🏼‍♂️|1369
-💆🏽‍♂️|1370
-💆🏾‍♂️|1371
-💆🏿‍♂️|1372
-🧖|1373
-🧖🏻|1374
-🧖🏼|1375
-🧖🏽|1376
-🧖🏾|1377
-🧖🏿|1378
-🧖‍♀️|1379
-🧖🏻‍♀️|1380
-🧖🏼‍♀️|1381
-🧖🏽‍♀️|1382
-🧖🏾‍♀️|1383
-🧖🏿‍♀️|1384
-🧖‍♂️|1385
-🧖🏻‍♂️|1386
-🧖🏼‍♂️|1387
-🧖🏽‍♂️|1388
-🧖🏾‍♂️|1389
-🧖🏿‍♂️|1390
-💅|1391
-💅🏻|1392
-💅🏼|1393
-💅🏽|1394
-💅🏾|1395
-💅🏿|1396
-🤳|1397
-🤳🏻|1398
-🤳🏼|1399
-🤳🏽|1400
-🤳🏾|1401
-🤳🏿|1402
-💃|1403
-💃🏻|1404
-💃🏼|1405
-💃🏽|1406
-💃🏾|1407
-💃🏿|1408
-🕺|1409
-🕺🏻|1410
-🕺🏼|1411
-🕺🏽|1412
-🕺🏿|1413
-🕺🏾|1414
-👯|1415
-👯‍♀️|1416
-👯‍♂️|1417
-🕴️|1418
-🕴🏻|1419
-🕴🏼|1420
-🕴🏽|1421
-🕴🏾|1422
-🕴🏿|1423
-🧑‍🦽|1424
-🧑🏻‍🦽|1425
-🧑🏼‍🦽|1426
-🧑🏽‍🦽|1427
-🧑🏾‍🦽|1428
-🧑🏿‍🦽|1429
-👩‍🦽|1430
-👩🏻‍🦽|1431
-👩🏼‍🦽|1432
-👩🏽‍🦽|1433
-👩🏾‍🦽|1434
-👩🏿‍🦽|1435
-👨‍🦽|1436
-👨🏻‍🦽|1437
-👨🏼‍🦽|1438
-👨🏽‍🦽|1439
-👨🏾‍🦽|1440
-👨🏿‍🦽|1441
-🧑‍🦼|1442
-🧑🏻‍🦼|1443
-🧑🏼‍🦼|1444
-🧑🏽‍🦼|1445
-🧑🏾‍🦼|1446
-🧑🏿‍🦼|1447
-👩‍🦼|1448
-👩🏻‍🦼|1449
-👩🏼‍🦼|1450
-👩🏽‍🦼|1451
-👩🏾‍🦼|1452
-👩🏿‍🦼|1453
-👨‍🦼|1454
-👨🏻‍🦼|1455
-👨🏼‍🦼|1456
-👨🏽‍🦼|1457
-👨🏾‍🦼|1458
-👨🏿‍🦼|1459
-🚶|1460
-🚶🏻|1461
-🚶🏼|1462
-🚶🏽|1463
-🚶🏾|1464
-🚶🏿|1465
-🚶‍♀️|1466
-🚶🏻‍♀️|1467
-🚶🏼‍♀️|1468
-🚶🏽‍♀️|1469
-🚶🏾‍♀️|1470
-🚶🏿‍♀️|1471
-🚶‍♂️|1472
-🚶🏻‍♂️|1473
-🚶🏼‍♂️|1474
-🚶🏽‍♂️|1475
-🚶🏾‍♂️|1476
-🚶🏿‍♂️|1477
-🧑‍🦯|1478
-🧑🏻‍🦯|1479
-🧑🏼‍🦯|1480
-🧑🏽‍🦯|1481
-🧑🏾‍🦯|1482
-🧑🏿‍🦯|1483
-👩‍🦯|1484
-👩🏻‍🦯|1485
-👩🏼‍🦯|1486
-👩🏽‍🦯|1487
-👩🏾‍🦯|1488
-👩🏿‍🦯|1489
-👨‍🦯|1490
-👨🏻‍🦯|1491
-👨🏽‍🦯|1492
-👨🏼‍🦯|1493
-👨🏾‍🦯|1494
-👨🏿‍🦯|1495
-🧎|1496
-🧎🏻|1497
-🧎🏼|1498
-🧎🏽|1499
-🧎🏾|1500
-🧎🏿|1501
-🧎‍♀️|1502
-🧎🏻‍♀️|1503
-🧎🏼‍♀️|1504
-🧎🏽‍♀️|1505
-🧎🏾‍♀️|1506
-🧎🏿‍♀️|1507
-🧎‍♂️|1508
-🧎🏻‍♂️|1509
-🧎🏼‍♂️|1510
-🧎🏽‍♂️|1511
-🧎🏾‍♂️|1512
-🧎🏿‍♂️|1513
-🏃|1514
-🏃🏻|1515
-🏃🏼|1516
-🏃🏽|1517
-🏃🏾|1518
-🏃🏿|1519
-🏃‍♀️|1520
-🏃🏻‍♀️|1521
-🏃🏼‍♀️|1522
-🏃🏽‍♀️|1523
-🏃🏾‍♀️|1524
-🏃🏿‍♀️|1525
-🏃‍♂️|1526
-🏃🏻‍♂️|1527
-🏃🏼‍♂️|1528
-🏃🏽‍♂️|1529
-🏃🏾‍♂️|1530
-🏃🏿‍♂️|1531
-🧍|1532
-🧍🏻|1533
-🧍🏼|1534
-🧍🏽|1535
-🧍🏾|1536
-🧍🏿|1537
-🧍‍♀️|1538
-🧍🏻‍♀️|1539
-🧍🏼‍♀️|1540
-🧍🏽‍♀️|1541
-🧍🏾‍♀️|1542
-🧍🏿‍♀️|1543
-🧍‍♂️|1544
-🧍🏻‍♂️|1545
-🧍🏼‍♂️|1546
-🧍🏽‍♂️|1547
-🧍🏾‍♂️|1548
-🧍🏿‍♂️|1549
-🧑‍🤝‍🧑|1550
-🧑🏻‍🤝‍🧑🏻|1551
-🧑🏼‍🤝‍🧑🏼|1557
-🧑🏽‍🤝‍🧑🏽|1563
-🧑🏾‍🤝‍🧑🏾|1569
-🧑🏿‍🤝‍🧑🏿|1575
-👫|1576
-👫🏻|1577
-👫🏼|1583
-👫🏽|1589
-👫🏾|1595
-👫🏿|1601
-👭|1602
-👭🏻|1603
-👭🏼|1609
-👭🏽|1615
-👭🏾|1621
-👭🏿|1627
-👬|1628
-👬🏻|1629
-👬🏼|1635
-👬🏽|1641
-👬🏾|1647
-👬🏿|1653
-💑|1654
-💑🏻|1655
-💑🏼|1661
-💑🏽|1667
-💑🏾|1673
-💑🏿|1679
-👩‍❤️‍👨|1680
-👩🏻‍❤️‍👨🏻|1681
-👩🏼‍❤️‍👨🏼|1687
-👩🏽‍❤️‍👨🏽|1693
-👩🏾‍❤️‍👨🏾|1699
-👩🏿‍❤️‍👨🏿|1705
-👩‍❤️‍👩|1706
-👩🏻‍❤️‍👩🏻|1707
-👩🏼‍❤️‍👩🏼|1713
-👩🏽‍❤️‍👩🏽|1719
-👩🏾‍❤️‍👩🏾|1725
-👩🏿‍❤️‍👩🏿|1731
-👨‍❤️‍👨|1732
-👨🏻‍❤️‍👨🏻|1733
-👨🏼‍❤️‍👨🏼|1739
-👨🏽‍❤️‍👨🏽|1745
-👨🏾‍❤️‍👨🏾|1751
-👨🏿‍❤️‍👨🏿|1757
-💏|1758
-💏🏻|1759
-💏🏼|1765
-💏🏽|1771
-💏🏾|1777
-💏🏿|1783
-👩‍❤️‍💋‍👨|1784
-👩🏻‍❤️‍💋‍👨🏻|1785
-👩🏼‍❤️‍💋‍👨🏼|1791
-👩🏽‍❤️‍💋‍👨🏽|1797
-👩🏾‍❤️‍💋‍👨🏾|1803
-👩🏿‍❤️‍💋‍👨🏿|1809
-👩‍❤️‍💋‍👩|1810
-👩🏻‍❤️‍💋‍👩🏻|1811
-👩🏼‍❤️‍💋‍👩🏼|1817
-👩🏽‍❤️‍💋‍👩🏽|1823
-👩🏾‍❤️‍💋‍👩🏾|1829
-👩🏿‍❤️‍💋‍👩🏿|1835
-👨‍❤️‍💋‍👨|1836
-👨🏻‍❤️‍💋‍👨🏻|1837
-👨🏼‍❤️‍💋‍👨🏼|1843
-👨🏽‍❤️‍💋‍👨🏽|1849
-👨🏾‍❤️‍💋‍👨🏾|1855
-👨🏿‍❤️‍💋‍👨🏿|1861
-👪|1862
-👨‍👩‍👦|1863
-👨‍👩‍👧|1864
-👨‍👩‍👧‍👦|1865
-👨‍👩‍👦‍👦|1866
-👨‍👩‍👧‍👧|1867
-👩‍👩‍👦|1868
-👩‍👩‍👧|1869
-👩‍👩‍👧‍👦|1870
-👩‍👩‍👦‍👦|1871
-👩‍👩‍👧‍👧|1872
-👨‍👨‍👦|1873
-👨‍👨‍👧|1874
-👨‍👨‍👧‍👦|1875
-👨‍👨‍👦‍👦|1876
-👨‍👨‍👧‍👧|1877
-👩‍👦|1878
-👩‍👧|1879
-👩‍👧‍👦|1880
-👩‍👦‍👦|1881
-👩‍👧‍👧|1882
-👨‍👦|1883
-👨‍👧|1884
-👨‍👧‍👦|1885
-👨‍👦‍👦|1886
-👨‍👧‍👧|1887
-🧶|1888
-🧵|1889
-🧥|1890
-🥼|1891
-🦺|1892
-👚|1893
-👕|1894
-👖|1895
-🩲|1896
-🩳|1897
-👔|1898
-👗|1899
-👙|1900
-🩱|1901
-👘|1902
-🥻|1903
-🥿|1904
-👠|1905
-👡|1906
-👢|1907
-👞|1908
-👟|1909
-🥾|1910
-🩴|1911
-🧦|1912
-🧤|1913
-🧣|1914
-🎩|1915
-🧢|1916
-👒|1917
-🎓|1918
-⛑️|1919
-🪖|1920
-👑|1921
-💍|1922
-👝|1923
-👛|1924
-👜|1925
-💼|1926
-🎒|1927
-🧳|1928
-👓|1929
-🕶️|1930
-🥽|1931
-🌂|1932
-🐶|1937
-🐱|1938
-🐭|1939
-🐹|1940
-🐰|1941
-🦊|1942
-🐻|1943
-🐼|1944
-🐻‍❄️|1945
-🐨|1946
-🐯|1947
-🦁|1948
-🐮|1949
-🐷|1950
-🐽|1951
-🐸|1952
-🐵|1953
-🙈|1954
-🙉|1955
-🙊|1956
-🐒|1957
-🐔|1958
-🐧|1959
-🐦|1960
-🐤|1961
-🐣|1962
-🐥|1963
-🦆|1964
-🦤|1965
-🦅|1966
-🦉|1967
-🦇|1968
-🐺|1969
-🐗|1970
-🐴|1971
-🦄|1972
-🐝|1973
-🐛|1974
-🦋|1975
-🐌|1976
-🪱|1977
-🐞|1978
-🐜|1979
-🪰|1980
-🦟|1981
-🪳|1982
-🪲|1983
-🦗|1984
-🕷️|1985
-🕸️|1986
-🦂|1987
-🐢|1988
-🐍|1989
-🦎|1990
-🦖|1991
-🦕|1992
-🐙|1993
-🦑|1994
-🦐|1995
-🦞|1996
-🦀|1997
-🐡|1998
-🐠|1999
-🐟|2000
-🦭|2001
-🐬|2002
-🐳|2003
-🐋|2004
-🦈|2005
-🐊|2006
-🐅|2007
-🐆|2008
-🦓|2009
-🦍|2010
-🦧|2011
-🐘|2012
-🦣|2013
-🦬|2014
-🦛|2015
-🦏|2016
-🐪|2017
-🐫|2018
-🦒|2019
-🦘|2020
-🐃|2021
-🐂|2022
-🐄|2023
-🐎|2024
-🐖|2025
-🐏|2026
-🐑|2027
-🦙|2028
-🐐|2029
-🦌|2030
-🐕|2031
-🐩|2032
-🦮|2033
-🐕‍🦺|2034
-🐈|2035
-🐈‍⬛|2036
-🐓|2037
-🦃|2038
-🦚|2039
-🦜|2040
-🦢|2041
-🦩|2042
-🕊️|2043
-🐇|2044
-🦝|2045
-🦨|2046
-🦡|2047
-🦫|2048
-🦦|2049
-🦥|2050
-🐁|2051
-🐀|2052
-🐿️|2053
-🦔|2054
-🐾|2055
-🐉|2056
-🐲|2057
-🌵|2058
-🎄|2059
-🌲|2060
-🌳|2061
-🌴|2062
-🌱|2063
-🌿|2064
-🍀|2066
-🎍|2067
-🎋|2068
-🍃|2069
-🍂|2070
-🍁|2071
-🪶|2072
-🍄|2073
-🐚|2074
-🪨|2075
-🪵|2076
-🌾|2077
-🪴|2078
-💐|2079
-🌷|2080
-🌹|2081
-🥀|2082
-🌺|2083
-🌸|2084
-🌼|2085
-🌻|2086
-🌞|2087
-🌝|2088
-🌛|2089
-🌜|2090
-🌚|2091
-🌕|2092
-🌖|2093
-🌗|2094
-🌘|2095
-🌑|2096
-🌒|2097
-🌓|2098
-🌔|2099
-🌙|2100
-🌎|2101
-🌍|2102
-🌏|2103
-🪐|2104
-💫|2105
-⭐|2106
-🌟|2107
-⚡|2109
-💥|2111
-🔥|2112
-🌪️|2113
-🌈|2114
-🌤️|2116
-⛅|2117
-🌥️|2118
-🌦️|2120
-🌧️|2121
-⛈️|2122
-🌩️|2123
-🌨️|2124
-⛄|2127
-🌬️|2128
-💨|2129
-💧|2130
-💦|2131
-🌊|2134
-🌫️|2135
-🍏|2136
-🍎|2137
-🍐|2138
-🍊|2139
-🍋|2140
-🍌|2141
-🍉|2142
-🍇|2143
-🫐|2144
-🍓|2145
-🍈|2146
-🍒|2147
-🍑|2148
-🥭|2149
-🍍|2150
-🥥|2151
-🥝|2152
-🍅|2153
-🍆|2154
-🥑|2155
-🫒|2156
-🥦|2157
-🥬|2158
-🫑|2159
-🥒|2160
-🌶️|2161
-🌽|2162
-🥕|2163
-🧄|2164
-🧅|2165
-🥔|2166
-🍠|2167
-🥐|2168
-🥯|2169
-🍞|2170
-🥖|2171
-🫓|2172
-🥨|2173
-🧀|2174
-🥚|2175
-🍳|2176
-🧈|2177
-🥞|2178
-🧇|2179
-🥓|2180
-🥩|2181
-🍗|2182
-🍖|2183
-🌭|2184
-🍔|2185
-🍟|2186
-🍕|2187
-🥪|2188
-🥙|2189
-🧆|2190
-🌮|2191
-🌯|2192
-🫔|2193
-🥗|2194
-🥘|2195
-🫕|2196
-🥫|2197
-🍝|2198
-🍜|2199
-🍲|2200
-🍛|2201
-🍣|2202
-🍱|2203
-🥟|2204
-🦪|2205
-🍤|2206
-🍙|2207
-🍚|2208
-🍘|2209
-🍥|2210
-🥠|2211
-🥮|2212
-🍢|2213
-🍡|2214
-🍧|2215
-🍨|2216
-🍦|2217
-🥧|2218
-🧁|2219
-🍰|2220
-🎂|2221
-🍮|2222
-🍭|2223
-🍬|2224
-🍫|2225
-🍿|2226
-🍩|2227
-🍪|2228
-🌰|2229
-🥜|2230
-🍯|2231
-🥛|2232
-🍼|2233
-🍵|2235
-🫖|2236
-🧉|2237
-🧋|2238
-🧃|2239
-🥤|2240
-🍶|2241
-🍺|2242
-🍻|2243
-🥂|2244
-🍷|2245
-🥃|2246
-🍸|2247
-🍹|2248
-🍾|2249
-🧊|2250
-🥄|2251
-🍴|2252
-🍽️|2253
-🥣|2254
-🥡|2255
-🥢|2256
-🧂|2257
-⚽|2258
-🏀|2259
-🏈|2260
-⚾|2261
-🥎|2262
-🎾|2263
-🏐|2264
-🏉|2265
-🥏|2266
-🪃|2267
-🎱|2268
-🪀|2269
-🏓|2270
-🏸|2271
-🏒|2272
-🏑|2273
-🥍|2274
-🏏|2275
-🥅|2276
-⛳|2277
-🪁|2278
-🏹|2279
-🎣|2280
-🤿|2281
-🥊|2282
-🥋|2283
-🎽|2284
-🛹|2285
-🛼|2286
-🛷|2287
-⛸️|2288
-🥌|2289
-🎿|2290
-⛷️|2291
-🏂|2292
-🪂|2298
-🏋|2299
-🏋🏻|2300
-🏋🏼|2301
-🏋🏽|2302
-🏋🏾|2303
-🏋🏿|2304
-🏋️‍♀️|2305
-🏋🏻‍♀️|2306
-🏋🏼‍♀️|2307
-🏋🏽‍♀️|2308
-🏋🏾‍♀️|2309
-🏋🏿‍♀️|2310
-🏋️‍♂️|2311
-🏋🏻‍♂️|2312
-🏋🏼‍♂️|2313
-🏋🏽‍♂️|2314
-🏋🏾‍♂️|2315
-🏋🏿‍♂️|2316
-🤼|2317
-🤼‍♀️|2318
-🤼‍♂️|2319
-🤸|2320
-🤸🏻|2321
-🤸🏼|2322
-🤸🏽|2323
-🤸🏾|2324
-🤸🏿|2325
-🤸‍♀️|2326
-🤸🏻‍♀️|2327
-🤸🏼‍♀️|2328
-🤸🏽‍♀️|2329
-🤸🏾‍♀️|2330
-🤸🏿‍♀️|2331
-🤸‍♂️|2332
-🤸🏻‍♂️|2333
-🤸🏼‍♂️|2334
-🤸🏽‍♂️|2335
-🤸🏾‍♂️|2336
-🤸🏿‍♂️|2337
-⛹|2338
-⛹🏻|2339
-⛹🏼|2340
-⛹🏽|2341
-⛹🏾|2342
-⛹🏿|2343
-⛹️‍♀️|2344
-⛹🏻‍♀️|2345
-⛹🏼‍♀️|2346
-⛹🏽‍♀️|2347
-⛹🏾‍♀️|2348
-⛹🏿‍♀️|2349
-⛹️‍♂️|2350
-⛹🏻‍♂️|2351
-⛹🏼‍♂️|2352
-⛹🏽‍♂️|2353
-⛹🏾‍♂️|2354
-⛹🏿‍♂️|2355
-🤺|2356
-🤾|2357
-🤾🏻|2358
-🤾🏼|2359
-🤾🏽|2360
-🤾🏾|2361
-🤾🏿|2362
-🤾‍♀️|2363
-🤾🏻‍♀️|2364
-🤾🏼‍♀️|2365
-🤾🏽‍♀️|2366
-🤾🏾‍♀️|2367
-🤾🏿‍♀️|2368
-🤾‍♂️|2369
-🤾🏻‍♂️|2370
-🤾🏼‍♂️|2371
-🤾🏽‍♂️|2372
-🤾🏾‍♂️|2373
-🤾🏿‍♂️|2374
-🏌|2375
-🏌🏻|2376
-🏌🏼|2377
-🏌🏽|2378
-🏌🏾|2379
-🏌🏿|2380
-🏌️‍♀️|2381
-🏌🏻‍♀️|2382
-🏌🏼‍♀️|2383
-🏌🏽‍♀️|2384
-🏌🏾‍♀️|2385
-🏌🏿‍♀️|2386
-🏌️‍♂️|2387
-🏌🏻‍♂️|2388
-🏌🏼‍♂️|2389
-🏌🏽‍♂️|2390
-🏌🏾‍♂️|2391
-🏌🏿‍♂️|2392
-🏇|2393
-🏇🏻|2394
-🏇🏼|2395
-🏇🏽|2396
-🏇🏾|2397
-🏇🏿|2398
-🧘|2399
-🧘🏻|2400
-🧘🏼|2401
-🧘🏽|2402
-🧘🏾|2403
-🧘🏿|2404
-🧘‍♀️|2405
-🧘🏻‍♀️|2406
-🧘🏼‍♀️|2407
-🧘🏽‍♀️|2408
-🧘🏾‍♀️|2409
-🧘🏿‍♀️|2410
-🧘‍♂️|2411
-🧘🏻‍♂️|2412
-🧘🏼‍♂️|2413
-🧘🏽‍♂️|2414
-🧘🏾‍♂️|2415
-🧘🏿‍♂️|2416
-🏄|2417
-🏄🏻|2418
-🏄🏼|2419
-🏄🏽|2420
-🏄🏾|2421
-🏄🏿|2422
-🏄‍♀️|2423
-🏄🏻‍♀️|2424
-🏄🏼‍♀️|2425
-🏄🏽‍♀️|2426
-🏄🏾‍♀️|2427
-🏄🏿‍♀️|2428
-🏄‍♂️|2429
-🏄🏻‍♂️|2430
-🏄🏼‍♂️|2431
-🏄🏽‍♂️|2432
-🏄🏾‍♂️|2433
-🏄🏿‍♂️|2434
-🏊|2435
-🏊🏻|2436
-🏊🏼|2437
-🏊🏽|2438
-🏊🏾|2439
-🏊🏿|2440
-🏊‍♀️|2441
-🏊🏻‍♀️|2442
-🏊🏼‍♀️|2443
-🏊🏽‍♀️|2444
-🏊🏾‍♀️|2445
-🏊🏿‍♀️|2446
-🏊‍♂️|2447
-🏊🏻‍♂️|2448
-🏊🏼‍♂️|2449
-🏊🏽‍♂️|2450
-🏊🏾‍♂️|2451
-🏊🏿‍♂️|2452
-🤽|2453
-🤽🏻|2454
-🤽🏼|2455
-🤽🏽|2456
-🤽🏾|2457
-🤽🏿|2458
-🤽‍♀️|2459
-🤽🏻‍♀️|2460
-🤽🏼‍♀️|2461
-🤽🏽‍♀️|2462
-🤽🏾‍♀️|2463
-🤽🏿‍♀️|2464
-🤽‍♂️|2465
-🤽🏻‍♂️|2466
-🤽🏼‍♂️|2467
-🤽🏽‍♂️|2468
-🤽🏾‍♂️|2469
-🤽🏿‍♂️|2470
-🚣|2471
-🚣🏻|2472
-🚣🏼|2473
-🚣🏽|2474
-🚣🏾|2475
-🚣🏿|2476
-🚣‍♀️|2477
-🚣🏻‍♀️|2478
-🚣🏼‍♀️|2479
-🚣🏽‍♀️|2480
-🚣🏾‍♀️|2481
-🚣🏿‍♀️|2482
-🚣‍♂️|2483
-🚣🏻‍♂️|2484
-🚣🏼‍♂️|2485
-🚣🏽‍♂️|2486
-🚣🏾‍♂️|2487
-🚣🏿‍♂️|2488
-🧗|2489
-🧗🏻|2490
-🧗🏼|2491
-🧗🏽|2492
-🧗🏾|2493
-🧗🏿|2494
-🧗‍♀️|2495
-🧗🏻‍♀️|2496
-🧗🏼‍♀️|2497
-🧗🏽‍♀️|2498
-🧗🏾‍♀️|2499
-🧗🏿‍♀️|2500
-🧗‍♂️|2501
-🧗🏻‍♂️|2502
-🧗🏼‍♂️|2503
-🧗🏽‍♂️|2504
-🧗🏾‍♂️|2505
-🧗🏿‍♂️|2506
-🚵|2507
-🚵🏻|2508
-🚵🏼|2509
-🚵🏽|2510
-🚵🏾|2511
-🚵🏿|2512
-🚵‍♀️|2513
-🚵🏻‍♀️|2514
-🚵🏼‍♀️|2515
-🚵🏽‍♀️|2516
-🚵🏾‍♀️|2517
-🚵🏿‍♀️|2518
-🚵‍♂️|2519
-🚵🏻‍♂️|2520
-🚵🏼‍♂️|2521
-🚵🏽‍♂️|2522
-🚵🏾‍♂️|2523
-🚵🏿‍♂️|2524
-🚴|2525
-🚴🏻|2526
-🚴🏼|2527
-🚴🏽|2528
-🚴🏾|2529
-🚴🏿|2530
-🚴‍♀️|2531
-🚴🏻‍♀️|2532
-🚴🏼‍♀️|2533
-🚴🏽‍♀️|2534
-🚴🏾‍♀️|2535
-🚴🏿‍♀️|2536
-🚴‍♂️|2537
-🚴🏻‍♂️|2538
-🚴🏼‍♂️|2539
-🚴🏽‍♂️|2540
-🚴🏾‍♂️|2541
-🚴🏿‍♂️|2542
-🏆|2543
-🥇|2544
-🥈|2545
-🥉|2546
-🏅|2547
-🎖️|2548
-🏵️|2549
-🎗️|2550
-🎫|2551
-🎟️|2552
-🎪|2553
-🤹|2554
-🤹🏻|2555
-🤹🏼|2556
-🤹🏽|2557
-🤹🏾|2558
-🤹🏿|2559
-🤹‍♀️|2560
-🤹🏻‍♀️|2561
-🤹🏼‍♀️|2562
-🤹🏽‍♀️|2563
-🤹🏾‍♀️|2564
-🤹🏿‍♀️|2565
-🤹‍♂️|2566
-🤹🏻‍♂️|2567
-🤹🏼‍♂️|2568
-🤹🏽‍♂️|2569
-🤹🏾‍♂️|2570
-🤹🏿‍♂️|2571
-🎭|2572
-🩰|2573
-🎨|2574
-🎬|2575
-🎤|2576
-🎧|2577
-🎼|2578
-🎹|2579
-🥁|2580
-🪘|2581
-🎷|2582
-🎺|2583
-🎸|2584
-🪕|2585
-🎻|2586
-🪗|2587
-🎲|2588
-♟️|2589
-🎯|2590
-🎳|2591
-🎮|2592
-🎰|2593
-🧩|2594
-🚗|2595
-🚕|2596
-🚙|2597
-🛻|2598
-🚌|2599
-🚎|2600
-🏎️|2601
-🚓|2602
-🚑|2603
-🚒|2604
-🚐|2605
-🚚|2606
-🚛|2607
-🚜|2608
-🦯|2609
-🦽|2610
-🦼|2611
-🛴|2612
-🚲|2613
-🛵|2614
-🏍️|2615
-🛺|2616
-🚨|2617
-🚔|2618
-🚍|2619
-🚘|2620
-🚖|2621
-🚡|2622
-🚠|2623
-🚟|2624
-🚃|2625
-🚋|2626
-🚞|2627
-🚝|2628
-🚄|2629
-🚅|2630
-🚈|2631
-🚂|2632
-🚆|2633
-🚇|2634
-🚊|2635
-🚉|2636
-🛫|2638
-🛬|2639
-🛩️|2640
-💺|2641
-🛰️|2642
-🚀|2643
-🛸|2644
-🚁|2645
-🛶|2646
-⛵|2647
-🚤|2648
-🛥️|2649
-🛳️|2650
-⛴️|2651
-🚢|2652
-⛽|2654
-🚧|2655
-🚦|2656
-🚥|2657
-🚏|2658
-🗺️|2659
-🗿|2660
-🗽|2661
-🗼|2662
-🏰|2663
-🏯|2664
-🏟️|2665
-🎡|2666
-🎢|2667
-🎠|2668
-⛲|2669
-⛱️|2670
-🏖️|2671
-🏝️|2672
-🏜️|2673
-🌋|2674
-⛰️|2675
-🏔️|2676
-🗻|2677
-🏕️|2678
-⛺|2679
-🏠|2680
-🏡|2681
-🏘️|2682
-🏚️|2683
-🛖|2684
-🏗️|2685
-🏭|2686
-🏢|2687
-🏬|2688
-🏣|2689
-🏤|2690
-🏥|2691
-🏦|2692
-🏨|2693
-🏪|2694
-🏫|2695
-🏩|2696
-💒|2697
-🏛️|2698
-⛪|2699
-🕌|2700
-🕍|2701
-🛕|2702
-🕋|2703
-⛩️|2704
-🛤️|2705
-🛣️|2706
-🗾|2707
-🎑|2708
-🏞️|2709
-🌅|2710
-🌄|2711
-🌠|2712
-🎇|2713
-🎆|2714
-🌇|2715
-🌆|2716
-🏙️|2717
-🌃|2718
-🌌|2719
-🌉|2720
-🌁|2721
-⌚|2722
-📱|2723
-📲|2724
-💻|2725
-🖥️|2727
-🖨️|2728
-🖱️|2729
-🖲️|2730
-🕹️|2731
-🗜️|2732
-💽|2733
-💾|2734
-💿|2735
-📀|2736
-📼|2737
-📷|2738
-📸|2739
-📹|2740
-🎥|2741
-📽️|2742
-🎞️|2743
-📞|2744
-☎️|2745
-📟|2746
-📠|2747
-📺|2748
-📻|2749
-🎙️|2750
-🎚️|2751
-🎛️|2752
-🧭|2753
-⏱️|2754
-⏲️|2755
-⏰|2756
-🕰️|2757
-⌛|2758
-⏳|2759
-📡|2760
-🔋|2761
-🔌|2762
-💡|2763
-🔦|2764
-🕯️|2765
-🪔|2766
-🧯|2767
-🛢️|2768
-💸|2769
-💵|2770
-💴|2771
-💶|2772
-💷|2773
-🪙|2774
-💰|2775
-💳|2776
-💎|2777
-🪜|2779
-🧰|2780
-🪛|2781
-🔧|2782
-🔨|2783
-🛠️|2785
-⛏️|2786
-🔩|2787
-🧱|2789
-⛓️|2790
-🪝|2791
-🪢|2792
-🧲|2793
-🔫|2794
-💣|2795
-🧨|2796
-🪓|2797
-🪚|2798
-🔪|2799
-🗡️|2800
-🛡️|2802
-🚬|2803
-⚰️|2804
-🪦|2805
-⚱️|2806
-🏺|2807
-🪄|2808
-🔮|2809
-📿|2810
-🧿|2811
-💈|2812
-🔭|2814
-🔬|2815
-🕳️|2816
-🪟|2817
-🩹|2818
-🩺|2819
-💊|2820
-💉|2821
-🩸|2822
-🧬|2823
-🦠|2824
-🧫|2825
-🧪|2826
-🌡️|2827
-🪤|2828
-🧹|2829
-🧺|2830
-🪡|2831
-🧻|2832
-🚽|2833
-🪠|2834
-🪣|2835
-🚰|2836
-🚿|2837
-🛁|2838
-🛀|2839
-🛀🏻|2840
-🛀🏼|2841
-🛀🏽|2842
-🛀🏾|2843
-🛀🏿|2844
-🪥|2845
-🧼|2846
-🪒|2847
-🧽|2848
-🧴|2849
-🛎️|2850
-🔑|2851
-🗝️|2852
-🚪|2853
-🪑|2854
-🪞|2855
-🛋️|2856
-🛏️|2857
-🛌|2858
-🧸|2864
-🖼️|2865
-🛍️|2866
-🛒|2867
-🎁|2868
-🎈|2869
-🎏|2870
-🎀|2871
-🎊|2872
-🎉|2873
-🪅|2874
-🪆|2875
-🎎|2876
-🏮|2877
-🎐|2878
-🧧|2879
-📩|2881
-📨|2882
-📧|2883
-💌|2884
-📥|2885
-📤|2886
-📦|2887
-🏷️|2888
-📪|2889
-📫|2890
-📬|2891
-📭|2892
-📮|2893
-📯|2894
-🪧|2895
-📜|2896
-📃|2897
-📄|2898
-📑|2899
-🧾|2900
-📊|2901
-📈|2902
-📉|2903
-🗒️|2904
-🗓️|2905
-📆|2906
-📅|2907
-🗑️|2908
-📇|2909
-🗃️|2910
-🗳️|2911
-🗄️|2912
-📋|2913
-📁|2914
-📂|2915
-🗂️|2916
-🗞️|2917
-📰|2918
-📓|2919
-📔|2920
-📒|2921
-📕|2922
-📗|2923
-📘|2924
-📙|2925
-📚|2926
-📖|2927
-🔖|2928
-🧷|2929
-🔗|2930
-📎|2931
-🖇️|2932
-📐|2933
-📏|2934
-🧮|2935
-📌|2936
-📍|2937
-🖊️|2939
-🖋️|2940
-🖌️|2942
-🖍️|2943
-📝|2944
-✏️|2945
-🔍|2946
-🔎|2947
-🔏|2948
-🔐|2949
-🔒|2950
-🔓|2951
-🧡|2953
-💛|2954
-💚|2955
-💙|2956
-💜|2957
-🖤|2958
-🤎|2959
-🤍|2960
-💔|2961
-💕|2963
-💞|2964
-💓|2965
-💗|2966
-💖|2967
-💘|2968
-💝|2969
-❤️‍🩹|2970
-❤️‍🔥|2971
-💟|2972
-☮️|2973
-✝️|2974
-☪️|2975
-🕉️|2976
-🔯|2979
-🕎|2980
-☯️|2981
-🛐|2983
-⛎|2984
-♊|2987
-♋|2988
-♌|2989
-♍|2990
-♎|2991
-♏|2992
-🆔|2997
-⚛️|2998
-🉑|2999
-📴|3002
-📳|3003
-🈶|3004
-🈚|3005
-🈸|3006
-🈺|3007
-🈷️|3008
-🆚|3010
-💮|3011
-🉐|3012
-🈴|3015
-🈵|3016
-🈹|3017
-🈲|3018
-🅰️|3019
-🅱️|3020
-🆎|3021
-🆑|3022
-🅾️|3023
-🆘|3024
-❌|3025
-⭕|3026
-🛑|3027
-⛔|3028
-📛|3029
-🚫|3030
-💯|3031
-💢|3032
-🚷|3034
-🚯|3035
-🚳|3036
-🚱|3037
-🔞|3038
-📵|3039
-🚭|3040
-‼️|3045
-🔅|3047
-🔆|3048
-〽️|3049
-⚠️|3050
-🚸|3051
-🔱|3052
-⚜️|3053
-🔰|3054
-♻️|3055
-🈯|3057
-💹|3058
-❎|3061
-🌐|3062
-💠|3063
-Ⓜ️|3064
-🌀|3065
-💤|3066
-🏧|3067
-🚾|3068
-♿|3069
-🅿️|3070
-🈳|3071
-🈂️|3072
-🛂|3073
-🛃|3074
-🛄|3075
-🛅|3076
-🛗|3077
-🚹|3078
-🚺|3079
-🚼|3080
-🚻|3081
-🚮|3082
-🎦|3083
-📶|3084
-🈁|3085
-🔣|3086
-🔤|3088
-🔡|3089
-🔠|3090
-🆖|3091
-🆗|3092
-🆙|3093
-🆒|3094
-🆕|3095
-🆓|3096
-0️⃣|3097
-1️⃣|3098
-2️⃣|3099
-3️⃣|3100
-4️⃣|3101
-5️⃣|3102
-6️⃣|3103
-7️⃣|3104
-8️⃣|3105
-9️⃣|3106
-🔟|3107
-🔢|3108
-#️⃣|3109
-*️⃣|3110
-⏏️|3111
-▶️|3112
-⏸️|3113
-⏯️|3114
-⏹️|3115
-⏺️|3116
-⏭️|3117
-⏮️|3118
-⏩|3119
-⏪|3120
-⏫|3121
-⏬|3122
-◀️|3123
-🔼|3124
-🔽|3125
-➡️|3126
-⬅️|3127
-⬆️|3128
-⬇️|3129
-↪️|3136
-↩️|3137
-🔀|3140
-🔁|3141
-🔂|3142
-🔄|3143
-🔃|3144
-🎵|3145
-🎶|3146
-♾️|3151
-💲|3152
-💱|3153
-©️|3155
-®️|3156
-➰|3158
-➿|3159
-🔚|3160
-🔙|3161
-🔛|3162
-🔝|3163
-🔜|3164
-🔘|3167
-⚪|3168
-⚫|3169
-🔴|3170
-🔵|3171
-🟤|3172
-🟣|3173
-🟢|3174
-🟡|3175
-🟠|3176
-🔺|3177
-🔻|3178
-🔸|3179
-🔹|3180
-🔶|3181
-🔷|3182
-🔳|3183
-🔲|3184
-▪️|3185
-▫️|3186
-◾|3187
-◽|3188
-◼️|3189
-◻️|3190
-⬛|3191
-⬜|3192
-🟧|3193
-🟦|3194
-🟥|3195
-🟫|3196
-🟪|3197
-🟩|3198
-🟨|3199
-🔈|3200
-🔇|3201
-🔉|3202
-🔊|3203
-🔔|3204
-🔕|3205
-📣|3206
-📢|3207
-🗨️|3208
-👁️‍🗨️|3209
-💬|3210
-💭|3211
-🗯️|3212
-🃏|3217
-🎴|3218
-🀄|3219
-🕐|3220
-🕑|3221
-🕒|3222
-🕓|3223
-🕔|3224
-🕕|3225
-🕖|3226
-🕗|3227
-🕘|3228
-🕙|3229
-🕚|3230
-🕛|3231
-🕜|3232
-🕝|3233
-🕞|3234
-🕟|3235
-🕠|3236
-🕡|3237
-🕢|3238
-🕣|3239
-🕤|3240
-🕥|3241
-🕦|3242
-🕧|3243
-⚧|3258
-🏳️|3260
-🏴|3261
-🏁|3262
-🚩|3263
-🏳️‍🌈|3264
-🏳️‍⚧️|3265
-🏴‍☠️|3266
-🇦🇫|3267
-🇦🇽|3268
-🇦🇱|3269
-🇩🇿|3270
-🇦🇸|3271
-🇦🇩|3272
-🇦🇴|3273
-🇦🇮|3274
-🇦🇶|3275
-🇦🇬|3276
-🇦🇷|3277
-🇦🇲|3278
-🇦🇼|3279
-🇦🇺|3280
-🇦🇹|3281
-🇦🇿|3282
-🇧🇸|3283
-🇧🇭|3284
-🇧🇩|3285
-🇧🇧|3286
-🇧🇾|3287
-🇧🇪|3288
-🇧🇿|3289
-🇧🇯|3290
-🇧🇲|3291
-🇧🇹|3292
-🇧🇴|3293
-🇧🇦|3294
-🇧🇼|3295
-🇧🇷|3296
-🇮🇴|3297
-🇻🇬|3298
-🇧🇳|3299
-🇧🇬|3300
-🇧🇫|3301
-🇧🇮|3302
-🇰🇭|3303
-🇨🇲|3304
-🇨🇦|3305
-🇮🇨|3306
-🇨🇻|3307
-🇧🇶|3308
-🇰🇾|3309
-🇨🇫|3310
-🇹🇩|3311
-🇨🇱|3312
-🇨🇳|3313
-🇨🇽|3314
-🇨🇨|3315
-🇨🇴|3316
-🇰🇲|3317
-🇨🇬|3318
-🇨🇩|3319
-🇨🇰|3320
-🇨🇷|3321
-🇨🇮|3322
-🇭🇷|3323
-🇨🇺|3324
-🇨🇼|3325
-🇨🇾|3326
-🇨🇿|3327
-🇩🇰|3328
-🇩🇯|3329
-🇩🇲|3330
-🇩🇴|3331
-🇪🇨|3332
-🇪🇬|3333
-🇸🇻|3334
-🇬🇶|3335
-🇪🇷|3336
-🇪🇪|3337
-🇪🇹|3338
-🇪🇺|3339
-🇫🇰|3340
-🇫🇴|3341
-🇫🇯|3342
-🇫🇮|3343
-🇫🇷|3344
-🇬🇫|3345
-🇵🇫|3346
-🇹🇫|3347
-🇬🇦|3348
-🇬🇲|3349
-🇬🇪|3350
-🇩🇪|3351
-🇬🇭|3352
-🇬🇮|3353
-🇬🇷|3354
-🇬🇱|3355
-🇬🇩|3356
-🇬🇵|3357
-🇬🇺|3358
-🇬🇹|3359
-🇬🇬|3360
-🇬🇳|3361
-🇬🇼|3362
-🇬🇾|3363
-🇭🇹|3364
-🇭🇳|3365
-🇭🇰|3366
-🇭🇺|3367
-🇮🇸|3368
-🇮🇳|3369
-🇮🇩|3370
-🇮🇷|3371
-🇮🇶|3372
-🇮🇪|3373
-🇮🇲|3374
-🇮🇱|3375
-🇮🇹|3376
-🇯🇲|3377
-🇯🇵|3378
-🎌|3379
-🇯🇪|3380
-🇯🇴|3381
-🇰🇿|3382
-🇰🇪|3383
-🇰🇮|3384
-🇽🇰|3385
-🇰🇼|3386
-🇰🇬|3387
-🇱🇦|3388
-🇱🇻|3389
-🇱🇧|3390
-🇱🇸|3391
-🇱🇷|3392
-🇱🇾|3393
-🇱🇮|3394
-🇱🇹|3395
-🇱🇺|3396
-🇲🇴|3397
-🇲🇰|3398
-🇲🇬|3399
-🇲🇼|3400
-🇲🇾|3401
-🇲🇻|3402
-🇲🇱|3403
-🇲🇹|3404
-🇲🇭|3405
-🇲🇶|3406
-🇲🇷|3407
-🇲🇺|3408
-🇾🇹|3409
-🇲🇽|3410
-🇫🇲|3411
-🇲🇩|3412
-🇲🇨|3413
-🇲🇳|3414
-🇲🇪|3415
-🇲🇸|3416
-🇲🇦|3417
-🇲🇿|3418
-🇲🇲|3419
-🇳🇦|3420
-🇳🇷|3421
-🇳🇵|3422
-🇳🇱|3423
-🇳🇨|3424
-🇳🇿|3425
-🇳🇮|3426
-🇳🇪|3427
-🇳🇬|3428
-🇳🇺|3429
-🇳🇫|3430
-🇰🇵|3431
-🇲🇵|3432
-🇳🇴|3433
-🇴🇲|3434
-🇵🇰|3435
-🇵🇼|3436
-🇵🇸|3437
-🇵🇦|3438
-🇵🇬|3439
-🇵🇾|3440
-🇵🇪|3441
-🇵🇭|3442
-🇵🇳|3443
-🇵🇱|3444
-🇵🇹|3445
-🇵🇷|3446
-🇶🇦|3447
-🇷🇪|3448
-🇷🇴|3449
-🇷🇺|3450
-🇷🇼|3451
-🇼🇸|3452
-🇸🇲|3453
-🇸🇹|3454
-🇸🇦|3455
-🇸🇳|3456
-🇷🇸|3457
-🇸🇨|3458
-🇸🇱|3459
-🇸🇬|3460
-🇸🇽|3461
-🇸🇰|3462
-🇸🇮|3463
-🇬🇸|3464
-🇸🇧|3465
-🇸🇴|3466
-🇿🇦|3467
-🇰🇷|3468
-🇸🇸|3469
-🇪🇸|3470
-🇱🇰|3471
-🇧🇱|3472
-🇸🇭|3473
-🇰🇳|3474
-🇱🇨|3475
-🇵🇲|3476
-🇻🇨|3477
-🇸🇩|3478
-🇸🇷|3479
-🇸🇿|3480
-🇸🇪|3481
-🇨🇭|3482
-🇸🇾|3483
-🇹🇼|3484
-🇹🇯|3485
-🇹🇿|3486
-🇹🇭|3487
-🇹🇱|3488
-🇹🇬|3489
-🇹🇰|3490
-🇹🇴|3491
-🇹🇹|3492
-🇹🇳|3493
-🇹🇷|3494
-🇹🇲|3495
-🇹🇨|3496
-🇻🇮|3497
-🇹🇻|3498
-🇺🇬|3499
-🇺🇦|3500
-🇦🇪|3501
-🇬🇧|3502
-🏴󠁧󠁢󠁥󠁮󠁧󠁿|3503
-🏴󠁧󠁢󠁳󠁣󠁴󠁿|3504
-🏴󠁧󠁢󠁷󠁬󠁳󠁿|3505
-🇺🇸|3506
-🇺🇾|3507
-🇺🇿|3508
-🇻🇺|3509
-🇻🇦|3510
-🇻🇪|3511
-🇻🇳|3512
-🇼🇫|3513
-🇪🇭|3514
-🇾🇪|3515
-🇿🇲|3516
-🇿🇼|3517
+😲|80
+😴|54
+🤤|53
+😪|52
+😵|64
+😵‍💫|65
+🤐|37
+🥴|63
+🤢|58
+🤮|59
+🤧|60
+😷|55
+🤒|56
+🤕|57
+🤑|29
+🤠|67
+🥸|69
+😈|103
+👿|104
+👹|109
+👺|110
+🤡|108
+💩|107
+👻|111
+💀|105
+👽|112
+👾|113
+🤖|114
+🎃|2803
+😺|115
+😸|116
+😹|117
+😻|118
+😼|119
+😽|120
+🙀|121
+😿|122
+😾|123
+🤲|374
+🤲🏻|375
+🤲🏼|376
+🤲🏽|377
+🤲🏾|378
+🤲🏿|379
+👐|368
+👐🏻|369
+👐🏼|370
+👐🏽|371
+👐🏾|372
+👐🏿|373
+🙌|356
+🙌🏻|357
+🙌🏼|358
+🙌🏽|359
+🙌🏾|360
+🙌🏿|361
+👏|350
+👏🏻|351
+👏🏼|352
+👏🏽|353
+👏🏾|354
+👏🏿|355
+🫠|11
+🫢|32
+🫣|33
+🫡|36
+🫥|42
+🫤|74
+🥹|83
+🫱|194
+🫱🏻|195
+🫱🏼|196
+🫱🏽|197
+🫱🏾|198
+🫱🏿|199
+🫲|200
+🫲🏻|201
+🫲🏼|202
+🫲🏽|203
+🫲🏾|204
+🫲🏿|205
+🫳|206
+🫳🏻|207
+🫳🏼|208
+🫳🏽|209
+🫳🏾|210
+🫳🏿|211
+🫴|212
+🫴🏻|213
+🫴🏼|214
+🫴🏽|215
+🫴🏾|216
+🫴🏿|217
+🫰|248
+🫰🏻|249
+🫰🏼|250
+🫰🏽|251
+🫰🏾|252
+🫰🏿|253
+🫵|308
+🫵🏻|309
+🫵🏼|310
+🫵🏽|311
+🫵🏾|312
+🫵🏿|313
+🫶|362
+🫶🏻|363
+🫶🏼|364
+🫶🏽|365
+🫶🏾|366
+🫶🏿|367
+🤝|380
+🤝🏻|381
+🤝🏼|382
+🤝🏽|383
+🤝🏾|384
+🤝🏿|385
+🫦|477
+🫅|1192
+🫅🏻|1193
+🫅🏼|1194
+🫅🏽|1195
+🫅🏾|1196
+🫅🏿|1197
+🫃|1282
+🫃🏻|1283
+🫃🏼|1284
+🫃🏽|1285
+🫃🏾|1286
+🫃🏿|1287
+🫄|1288
+🫄🏻|1289
+🫄🏼|1290
+🫄🏽|1291
+🫄🏾|1292
+🫄🏿|1293
+🧌|1474
+🪸|2410
+🪷|2430
+🪹|2451
+🪺|2452
+🫘|2486
+🫗|2572
+🫙|2583
+🛝|2646
+🛞|2695
+🛟|2702
+🪬|2864
+🪩|2872
+🪫|2968
+🩼|3109
+🩻|3111
+🫧|3132
+🪪|3143
+🟰|3247
+👍|314
+👍🏻|315
+👍🏼|316
+👍🏽|317
+👍🏾|318
+👍🏿|319
+👎|320
+👎🏻|321
+👎🏼|322
+👎🏽|323
+👎🏾|324
+👎🏿|325
+👊|332
+👊🏻|333
+👊🏼|334
+👊🏽|335
+👊🏾|336
+👊🏿|337
+✊|326
+✊🏻|327
+✊🏼|328
+✊🏽|329
+✊🏾|330
+✊🏿|331
+🤛|338
+🤛🏻|339
+🤛🏼|340
+🤛🏽|341
+🤛🏾|342
+🤛🏿|343
+🤜|344
+🤜🏻|345
+🤜🏼|346
+🤜🏽|347
+🤜🏾|348
+🤜🏿|349
+🤞|242
+🤞🏻|243
+🤞🏼|244
+🤞🏽|245
+🤞🏾|246
+🤞🏿|247
+✌️|236
+✌🏻|237
+✌🏼|238
+✌🏽|239
+✌🏾|240
+✌🏿|241
+🤟|254
+🤟🏻|255
+🤟🏼|256
+🤟🏽|257
+🤟🏾|258
+🤟🏿|259
+🤘|260
+🤘🏻|261
+🤘🏼|262
+🤘🏽|263
+🤘🏾|264
+🤘🏿|265
+👌|218
+👌🏻|219
+👌🏼|220
+👌🏽|221
+👌🏾|222
+👌🏿|223
+🤏|230
+🤏🏻|231
+🤏🏼|232
+🤏🏽|233
+🤏🏾|234
+🤏🏿|235
+🤌|224
+🤌🏼|226
+🤌🏻|225
+🤌🏽|227
+🤌🏾|228
+🤌🏿|229
+👈|272
+👈🏻|273
+👈🏼|274
+👈🏽|275
+👈🏾|276
+👈🏿|277
+👉|278
+👉🏻|279
+👉🏼|280
+👉🏽|281
+👉🏾|282
+👉🏿|283
+👆|284
+👆🏻|285
+👆🏼|286
+👆🏽|287
+👆🏾|288
+👆🏿|289
+👇|296
+👇🏻|297
+👇🏼|298
+👇🏽|299
+👇🏾|300
+👇🏿|301
+☝️|302
+☝🏻|303
+☝🏼|304
+☝🏽|305
+☝🏾|306
+☝🏿|307
+✋|182
+✋🏻|183
+✋🏼|184
+✋🏽|185
+✋🏾|186
+✋🏿|187
+🤚|170
+🤚🏻|171
+🤚🏼|172
+🤚🏽|173
+🤚🏾|174
+🤚🏿|175
+🖐|176
+🖐🏻|177
+🖐🏼|178
+🖐🏽|179
+🖐🏾|180
+🖐🏿|181
+🖖|188
+🖖🏻|189
+🖖🏼|190
+🖖🏽|191
+🖖🏾|192
+🖖🏿|193
+👋|164
+👋🏻|165
+👋🏼|166
+👋🏽|167
+👋🏾|168
+👋🏿|169
+🤙|266
+🤙🏻|267
+🤙🏼|268
+🤙🏽|269
+🤙🏾|270
+🤙🏿|271
+💪|430
+💪🏻|431
+💪🏼|432
+💪🏽|433
+💪🏾|434
+💪🏿|435
+🦾|436
+🖕|290
+🖕🏻|291
+🖕🏼|292
+🖕🏽|293
+🖕🏾|294
+🖕🏿|295
+✍️|412
+✍🏻|413
+✍🏼|414
+✍🏽|415
+✍🏾|416
+✍🏿|417
+🙏|406
+🙏🏻|407
+🙏🏼|408
+🙏🏽|409
+🙏🏾|410
+🙏🏿|411
+🦶|444
+🦶🏻|445
+🦶🏼|446
+🦶🏽|447
+🦶🏾|448
+🦶🏿|449
+🦵|438
+🦵🏻|439
+🦵🏼|440
+🦵🏽|441
+🦵🏾|442
+🦵🏿|443
+🦿|437
+💄|2931
+💋|127
+👄|476
+🦷|471
+👅|475
+👂|450
+👂🏻|451
+👂🏼|452
+👂🏽|453
+👂🏾|454
+👂🏿|455
+🦻|456
+🦻🏻|457
+🦻🏼|458
+🦻🏽|459
+🦻🏾|460
+🦻🏿|461
+👃|462
+👃🏻|463
+👃🏼|464
+👃🏽|465
+👃🏾|466
+👃🏿|467
+👣|2299
+👁️|474
+👀|473
+🧠|468
+🫀|469
+🫁|470
+🦴|472
+🗣️|2295
+👤|2296
+👥|2297
+🫂|2298
+👶|478
+👶🏻|479
+👶🏼|480
+👶🏽|481
+👶🏾|482
+👶🏿|483
+👧|496
+👧🏻|497
+👧🏼|498
+👧🏽|499
+👧🏾|500
+👧🏿|501
+🧒|484
+🧒🏻|485
+🧒🏼|486
+🧒🏽|487
+🧒🏾|488
+🧒🏿|489
+👦|490
+👦🏻|491
+👦🏼|492
+👦🏽|493
+👦🏾|494
+👦🏿|495
+👩|562
+👩🏻|563
+👩🏼|564
+👩🏽|565
+👩🏾|566
+👩🏿|567
+🧑|502
+🧑🏻|503
+🧑🏼|504
+🧑🏽|505
+🧑🏾|506
+🧑🏿|507
+👨|514
+👨🏻|515
+👨🏼|516
+👨🏽|517
+👨🏾|518
+👨🏿|519
+🧑‍🦱|586
+🧑🏻‍🦱|587
+🧑🏼‍🦱|588
+🧑🏽‍🦱|589
+🧑🏾‍🦱|590
+🧑🏿‍🦱|591
+👩‍🦱|580
+👩🏻‍🦱|581
+👩🏼‍🦱|582
+👩🏽‍🦱|583
+👩🏾‍🦱|584
+👩🏿‍🦱|585
+👨‍🦱|544
+👨🏻‍🦱|545
+👨🏼‍🦱|546
+👨🏽‍🦱|547
+👨🏾‍🦱|548
+👨🏿‍🦱|549
+🧑‍🦰|574
+🧑🏻‍🦰|575
+🧑🏼‍🦰|576
+🧑🏽‍🦰|577
+🧑🏾‍🦰|578
+🧑🏿‍🦰|579
+👩‍🦰|568
+👩🏻‍🦰|569
+👩🏼‍🦰|570
+👩🏽‍🦰|571
+👩🏾‍🦰|572
+👩🏿‍🦰|573
+👨‍🦰|538
+👨🏻‍🦰|539
+👨🏼‍🦰|540
+👨🏽‍🦰|541
+👨🏾‍🦰|542
+👨🏿‍🦰|543
+👱‍♀️|616
+👱🏻‍♀️|617
+👱🏼‍♀️|618
+👱🏽‍♀️|619
+👱🏾‍♀️|620
+👱🏿‍♀️|621
+👱|508
+👱🏻|509
+👱🏼|510
+👱🏽|511
+👱🏾|512
+👱🏿|513
+👱‍♂️|622
+👱🏻‍♂️|623
+👱🏼‍♂️|624
+👱🏽‍♂️|625
+👱🏾‍♂️|626
+👱🏿‍♂️|627
+🧑‍🦳|598
+🧑🏻‍🦳|599
+🧑🏼‍🦳|600
+🧑🏽‍🦳|601
+🧑🏾‍🦳|602
+🧑🏿‍🦳|603
+👩‍🦳|592
+👩🏻‍🦳|593
+👩🏼‍🦳|594
+👩🏽‍🦳|595
+👩🏾‍🦳|596
+👩🏿‍🦳|597
+👨‍🦳|550
+👨🏻‍🦳|551
+👨🏼‍🦳|552
+👨🏽‍🦳|553
+👨🏾‍🦳|554
+👨🏿‍🦳|555
+🧑‍🦲|610
+🧑🏻‍🦲|611
+🧑🏼‍🦲|612
+🧑🏽‍🦲|613
+🧑🏾‍🦲|614
+🧑🏿‍🦲|615
+👩‍🦲|604
+👩🏻‍🦲|605
+👩🏼‍🦲|606
+👩🏽‍🦲|607
+👩🏾‍🦲|608
+👩🏿‍🦲|609
+👨‍🦲|556
+👨🏻‍🦲|557
+👨🏼‍🦲|558
+👨🏽‍🦲|559
+👨🏾‍🦲|560
+👨🏿‍🦲|561
+🧔|520
+🧔🏻|521
+🧔🏼|522
+🧔🏽|523
+🧔🏾|524
+🧔🏿|525
+🧔‍♂️|526
+🧔🏻‍♂️|527
+🧔🏼‍♂️|528
+🧔🏽‍♂️|529
+🧔🏾‍♂️|530
+🧔🏿‍♂️|531
+🧔‍♀️|532
+🧔🏻‍♀️|533
+🧔🏼‍♀️|534
+🧔🏽‍♀️|535
+🧔🏾‍♀️|536
+🧔🏿‍♀️|537
+👵|640
+👵🏻|641
+👵🏼|642
+👵🏽|643
+👵🏾|644
+👵🏿|645
+🧓|628
+🧓🏻|629
+🧓🏼|630
+🧓🏽|631
+🧓🏾|632
+🧓🏿|633
+👴|634
+👴🏻|635
+👴🏼|636
+👴🏽|637
+👴🏾|638
+👴🏿|639
+👲|1228
+👲🏻|1229
+👲🏼|1230
+👲🏽|1231
+👲🏾|1232
+👲🏿|1233
+👳|1210
+👳🏻|1211
+👳🏼|1212
+👳🏽|1213
+👳🏾|1214
+👳🏿|1215
+👳‍♀️|1222
+👳🏻‍♀️|1223
+👳🏼‍♀️|1224
+👳🏽‍♀️|1225
+👳🏾‍♀️|1226
+👳🏿‍♀️|1227
+👳‍♂️|1216
+👳🏻‍♂️|1217
+👳🏼‍♂️|1218
+👳🏽‍♂️|1219
+👳🏾‍♂️|1220
+👳🏿‍♂️|1221
+🧕|1234
+🧕🏻|1235
+🧕🏼|1236
+🧕🏽|1237
+🧕🏾|1238
+🧕🏿|1239
+👮|1114
+👮🏻|1115
+👮🏼|1116
+👮🏽|1117
+👮🏾|1118
+👮🏿|1119
+👮‍♀️|1126
+👮🏻‍♀️|1127
+👮🏼‍♀️|1128
+👮🏽‍♀️|1129
+👮🏾‍♀️|1130
+👮🏿‍♀️|1131
+👮‍♂️|1120
+👮🏻‍♂️|1121
+👮🏼‍♂️|1122
+👮🏽‍♂️|1123
+👮🏾‍♂️|1124
+👮🏿‍♂️|1125
+👷|1174
+👷🏻|1175
+👷🏼|1176
+👷🏽|1177
+👷🏾|1178
+👷🏿|1179
+👷‍♀️|1186
+👷🏻‍♀️|1187
+👷🏼‍♀️|1188
+👷🏽‍♀️|1189
+👷🏾‍♀️|1190
+👷🏿‍♀️|1191
+👷‍♂️|1180
+👷🏻‍♂️|1181
+👷🏼‍♂️|1182
+👷🏽‍♂️|1183
+👷🏾‍♂️|1184
+👷🏿‍♂️|1185
+💂|1150
+💂🏻|1151
+💂🏼|1152
+💂🏽|1153
+💂🏾|1154
+💂🏿|1155
+💂‍♀️|1162
+💂🏻‍♀️|1163
+💂🏼‍♀️|1164
+💂🏽‍♀️|1165
+💂🏾‍♀️|1166
+💂🏿‍♀️|1167
+💂‍♂️|1156
+💂🏻‍♂️|1157
+💂🏼‍♂️|1158
+💂🏽‍♂️|1159
+💂🏾‍♂️|1160
+💂🏿‍♂️|1161
+🕵|1132
+🕵🏻|1133
+🕵🏼|1134
+🕵🏽|1135
+🕵🏾|1136
+🕵🏿|1137
+🕵️‍♀️|1144
+🕵🏻‍♀️|1145
+🕵🏼‍♀️|1146
+🕵🏽‍♀️|1147
+🕵🏾‍♀️|1148
+🕵🏿‍♀️|1149
+🕵️‍♂️|1138
+🕵🏻‍♂️|1139
+🕵🏼‍♂️|1140
+🕵🏽‍♂️|1141
+🕵🏾‍♂️|1142
+🕵🏿‍♂️|1143
+🧑‍⚕️|826
+🧑🏻‍⚕️|827
+🧑🏼‍⚕️|828
+🧑🏽‍⚕️|829
+🧑🏾‍⚕️|830
+🧑🏿‍⚕️|831
+👩‍⚕️|838
+👩🏻‍⚕️|839
+👩🏼‍⚕️|840
+👩🏽‍⚕️|841
+👩🏾‍⚕️|842
+👩🏿‍⚕️|843
+👨‍⚕️|832
+👨🏻‍⚕️|833
+👨🏼‍⚕️|834
+👨🏽‍⚕️|835
+👨🏾‍⚕️|836
+👨🏿‍⚕️|837
+🧑‍🌾|898
+🧑🏻‍🌾|899
+🧑🏼‍🌾|900
+🧑🏽‍🌾|901
+🧑🏾‍🌾|902
+🧑🏿‍🌾|903
+👩‍🌾|910
+👩🏻‍🌾|911
+👩🏼‍🌾|912
+👩🏽‍🌾|913
+👩🏾‍🌾|914
+👩🏿‍🌾|915
+👨‍🌾|904
+👨🏻‍🌾|905
+👨🏼‍🌾|906
+👨🏽‍🌾|907
+👨🏾‍🌾|908
+👨🏿‍🌾|909
+🧑‍🍳|916
+🧑🏻‍🍳|917
+🧑🏼‍🍳|918
+🧑🏽‍🍳|919
+🧑🏾‍🍳|920
+🧑🏿‍🍳|921
+👩‍🍳|928
+👩🏻‍🍳|929
+👩🏼‍🍳|930
+👩🏽‍🍳|931
+👩🏾‍🍳|932
+👩🏿‍🍳|933
+👨‍🍳|922
+👨🏻‍🍳|923
+👨🏼‍🍳|924
+👨🏽‍🍳|925
+👨🏾‍🍳|926
+👨🏿‍🍳|927
+🧑‍🎓|844
+🧑🏻‍🎓|845
+🧑🏼‍🎓|846
+🧑🏽‍🎓|847
+🧑🏾‍🎓|848
+🧑🏿‍🎓|849
+👩‍🎓|856
+👩🏻‍🎓|857
+👩🏼‍🎓|858
+👩🏽‍🎓|859
+👩🏾‍🎓|860
+👩🏿‍🎓|861
+👨‍🎓|850
+👨🏻‍🎓|851
+👨🏼‍🎓|852
+👨🏽‍🎓|853
+👨🏾‍🎓|854
+👨🏿‍🎓|855
+🧑‍🎤|1024
+🧑🏻‍🎤|1025
+🧑🏼‍🎤|1026
+🧑🏽‍🎤|1027
+🧑🏾‍🎤|1028
+🧑🏿‍🎤|1029
+👩‍🎤|1036
+👩🏻‍🎤|1037
+👩🏼‍🎤|1038
+👩🏽‍🎤|1039
+👩🏾‍🎤|1040
+👩🏿‍🎤|1041
+👨‍🎤|1030
+👨🏻‍🎤|1031
+👨🏼‍🎤|1032
+👨🏽‍🎤|1033
+👨🏾‍🎤|1034
+👨🏿‍🎤|1035
+🧑‍🏫|862
+🧑🏻‍🏫|863
+🧑🏼‍🏫|864
+🧑🏽‍🏫|865
+🧑🏾‍🏫|866
+🧑🏿‍🏫|867
+👩‍🏫|874
+👩🏻‍🏫|875
+👩🏼‍🏫|876
+👩🏽‍🏫|877
+👩🏾‍🏫|878
+👩🏿‍🏫|879
+👨‍🏫|868
+👨🏻‍🏫|869
+👨🏼‍🏫|870
+👨🏽‍🏫|871
+👨🏾‍🏫|872
+👨🏿‍🏫|873
+🧑‍🏭|952
+🧑🏻‍🏭|953
+🧑🏼‍🏭|954
+🧑🏽‍🏭|955
+🧑🏾‍🏭|956
+🧑🏿‍🏭|957
+👩‍🏭|964
+👩🏻‍🏭|965
+👩🏼‍🏭|966
+👩🏽‍🏭|967
+👩🏾‍🏭|968
+👩🏿‍🏭|969
+👨‍🏭|958
+👨🏻‍🏭|959
+👨🏼‍🏭|960
+👨🏽‍🏭|961
+👨🏾‍🏭|962
+👨🏿‍🏭|963
+🧑‍💻|1006
+🧑🏻‍💻|1007
+🧑🏼‍💻|1008
+🧑🏽‍💻|1009
+🧑🏾‍💻|1010
+🧑🏿‍💻|1011
+👩‍💻|1018
+👩🏻‍💻|1019
+👩🏼‍💻|1020
+👩🏽‍💻|1021
+👩🏾‍💻|1022
+👩🏿‍💻|1023
+👨‍💻|1012
+👨🏻‍💻|1013
+👨🏼‍💻|1014
+👨🏽‍💻|1015
+👨🏾‍💻|1016
+👨🏿‍💻|1017
+🧑‍💼|970
+🧑🏻‍💼|971
+🧑🏼‍💼|972
+🧑🏽‍💼|973
+🧑🏾‍💼|974
+🧑🏿‍💼|975
+👩‍💼|982
+👩🏻‍💼|983
+👩🏼‍💼|984
+👩🏽‍💼|985
+👩🏾‍💼|986
+👩🏿‍💼|987
+👨‍💼|976
+👨🏻‍💼|977
+👨🏼‍💼|978
+👨🏽‍💼|979
+👨🏾‍💼|980
+👨🏿‍💼|981
+🧑‍🔧|934
+🧑🏻‍🔧|935
+🧑🏼‍🔧|936
+🧑🏽‍🔧|937
+🧑🏾‍🔧|938
+🧑🏿‍🔧|939
+👩‍🔧|946
+👩🏻‍🔧|947
+👩🏼‍🔧|948
+👩🏽‍🔧|949
+👩🏾‍🔧|950
+👩🏿‍🔧|951
+👨‍🔧|940
+👨🏻‍🔧|941
+👨🏼‍🔧|942
+👨🏽‍🔧|943
+👨🏾‍🔧|944
+👨🏿‍🔧|945
+🧑‍🔬|988
+🧑🏻‍🔬|989
+🧑🏼‍🔬|990
+🧑🏽‍🔬|991
+🧑🏾‍🔬|992
+🧑🏿‍🔬|993
+👩‍🔬|1000
+👩🏻‍🔬|1001
+👩🏼‍🔬|1002
+👩🏽‍🔬|1003
+👩🏾‍🔬|1004
+👩🏿‍🔬|1005
+👨‍🔬|994
+👨🏻‍🔬|995
+👨🏼‍🔬|996
+👨🏽‍🔬|997
+👨🏾‍🔬|998
+👨🏿‍🔬|999
+🧑‍🎨|1042
+🧑🏻‍🎨|1043
+🧑🏼‍🎨|1044
+🧑🏽‍🎨|1045
+🧑🏾‍🎨|1046
+🧑🏿‍🎨|1047
+👩‍🎨|1054
+👩🏻‍🎨|1055
+👩🏼‍🎨|1056
+👩🏽‍🎨|1057
+👩🏾‍🎨|1058
+👩🏿‍🎨|1059
+👨‍🎨|1048
+👨🏻‍🎨|1049
+👨🏼‍🎨|1050
+👨🏽‍🎨|1051
+👨🏾‍🎨|1052
+👨🏿‍🎨|1053
+🧑‍🚒|1096
+🧑🏻‍🚒|1097
+🧑🏼‍🚒|1098
+🧑🏽‍🚒|1099
+🧑🏾‍🚒|1100
+🧑🏿‍🚒|1101
+👩‍🚒|1108
+👩🏻‍🚒|1109
+👩🏼‍🚒|1110
+👩🏽‍🚒|1111
+👩🏾‍🚒|1112
+👩🏿‍🚒|1113
+👨‍🚒|1102
+👨🏻‍🚒|1103
+👨🏼‍🚒|1104
+👨🏽‍🚒|1105
+👨🏾‍🚒|1106
+👨🏿‍🚒|1107
+🧑‍✈️|1060
+🧑🏻‍✈️|1061
+🧑🏼‍✈️|1062
+🧑🏽‍✈️|1063
+🧑🏾‍✈️|1064
+🧑🏿‍✈️|1065
+👩‍✈️|1072
+👩🏻‍✈️|1073
+👩🏼‍✈️|1074
+👩🏽‍✈️|1075
+👩🏾‍✈️|1076
+👩🏿‍✈️|1077
+👨‍✈️|1066
+👨🏻‍✈️|1067
+👨🏼‍✈️|1068
+👨🏽‍✈️|1069
+👨🏾‍✈️|1070
+👨🏿‍✈️|1071
+🧑‍🚀|1078
+🧑🏻‍🚀|1079
+🧑🏼‍🚀|1080
+🧑🏽‍🚀|1081
+🧑🏾‍🚀|1082
+🧑🏿‍🚀|1083
+👩‍🚀|1090
+👩🏻‍🚀|1091
+👩🏼‍🚀|1092
+👩🏽‍🚀|1093
+👩🏾‍🚀|1094
+👩🏿‍🚀|1095
+👨‍🚀|1084
+👨🏻‍🚀|1085
+👨🏼‍🚀|1086
+👨🏽‍🚀|1087
+👨🏾‍🚀|1088
+👨🏿‍🚀|1089
+🧑‍⚖️|880
+🧑🏻‍⚖️|881
+🧑🏼‍⚖️|882
+🧑🏽‍⚖️|883
+🧑🏾‍⚖️|884
+🧑🏿‍⚖️|885
+👩‍⚖️|892
+👩🏻‍⚖️|893
+👩🏼‍⚖️|894
+👩🏽‍⚖️|895
+👩🏾‍⚖️|896
+👩🏿‍⚖️|897
+👨‍⚖️|886
+👨🏻‍⚖️|887
+👨🏼‍⚖️|888
+👨🏽‍⚖️|889
+👨🏾‍⚖️|890
+👨🏿‍⚖️|891
+👰|1258
+👰🏻|1259
+👰🏼|1260
+👰🏽|1261
+👰🏾|1262
+👰🏿|1263
+👰‍♀️|1270
+👰🏻‍♀️|1271
+👰🏼‍♀️|1272
+👰🏽‍♀️|1273
+👰🏾‍♀️|1274
+👰🏿‍♀️|1275
+👰‍♂️|1264
+👰🏻‍♂️|1265
+👰🏼‍♂️|1266
+👰🏽‍♂️|1267
+👰🏾‍♂️|1268
+👰🏿‍♂️|1269
+🤵|1240
+🤵🏻|1241
+🤵🏼|1242
+🤵🏽|1243
+🤵🏾|1244
+🤵🏿|1245
+🤵‍♀️|1252
+🤵🏻‍♀️|1253
+🤵🏼‍♀️|1254
+🤵🏽‍♀️|1255
+🤵🏾‍♀️|1256
+🤵🏿‍♀️|1257
+🤵‍♂️|1246
+🤵🏻‍♂️|1247
+🤵🏼‍♂️|1248
+🤵🏽‍♂️|1249
+🤵🏾‍♂️|1250
+🤵🏿‍♂️|1251
+👸|1204
+👸🏻|1205
+👸🏼|1206
+👸🏽|1207
+👸🏾|1208
+👸🏿|1209
+🤴|1198
+🤴🏻|1199
+🤴🏼|1200
+🤴🏽|1201
+🤴🏾|1202
+🤴🏿|1203
+🦸|1342
+🦸🏻|1343
+🦸🏼|1344
+🦸🏽|1345
+🦸🏾|1346
+🦸🏿|1347
+🦸‍♀️|1354
+🦸🏻‍♀️|1355
+🦸🏼‍♀️|1356
+🦸🏽‍♀️|1357
+🦸🏾‍♀️|1358
+🦸🏿‍♀️|1359
+🦸‍♂️|1348
+🦸🏻‍♂️|1349
+🦸🏼‍♂️|1350
+🦸🏽‍♂️|1351
+🦸🏾‍♂️|1352
+🦸🏿‍♂️|1353
+🦹|1360
+🦹🏻|1361
+🦹🏼|1362
+🦹🏽|1363
+🦹🏾|1364
+🦹🏿|1365
+🦹‍♀️|1372
+🦹🏻‍♀️|1373
+🦹🏼‍♀️|1374
+🦹🏽‍♀️|1375
+🦹🏾‍♀️|1376
+🦹🏿‍♀️|1377
+🦹‍♂️|1366
+🦹🏻‍♂️|1367
+🦹🏼‍♂️|1368
+🦹🏽‍♂️|1369
+🦹🏾‍♂️|1370
+🦹🏿‍♂️|1371
+🥷|1168
+🥷🏻|1169
+🥷🏼|1170
+🥷🏽|1171
+🥷🏾|1172
+🥷🏿|1173
+🧑‍🎄|1336
+🧑🏻‍🎄|1337
+🧑🏼‍🎄|1338
+🧑🏽‍🎄|1339
+🧑🏾‍🎄|1340
+🧑🏿‍🎄|1341
+🤶|1330
+🤶🏻|1331
+🤶🏼|1332
+🤶🏽|1333
+🤶🏾|1334
+🤶🏿|1335
+🎅|1324
+🎅🏻|1325
+🎅🏼|1326
+🎅🏽|1327
+🎅🏾|1328
+🎅🏿|1329
+🧙|1378
+🧙🏻|1379
+🧙🏼|1380
+🧙🏽|1381
+🧙🏾|1382
+🧙🏿|1383
+🧙‍♀️|1390
+🧙🏻‍♀️|1391
+🧙🏼‍♀️|1392
+🧙🏽‍♀️|1393
+🧙🏾‍♀️|1394
+🧙🏿‍♀️|1395
+🧙‍♂️|1384
+🧙🏻‍♂️|1385
+🧙🏼‍♂️|1386
+🧙🏽‍♂️|1387
+🧙🏾‍♂️|1388
+🧙🏿‍♂️|1389
+🧝|1450
+🧝🏻|1451
+🧝🏼|1452
+🧝🏽|1453
+🧝🏾|1454
+🧝🏿|1455
+🧝‍♀️|1462
+🧝🏻‍♀️|1463
+🧝🏼‍♀️|1464
+🧝🏽‍♀️|1465
+🧝🏾‍♀️|1466
+🧝🏿‍♀️|1467
+🧝‍♂️|1456
+🧝🏻‍♂️|1457
+🧝🏼‍♂️|1458
+🧝🏽‍♂️|1459
+🧝🏾‍♂️|1460
+🧝🏿‍♂️|1461
+🧛|1414
+🧛🏻|1415
+🧛🏼|1416
+🧛🏽|1417
+🧛🏾|1418
+🧛🏿|1419
+🧛‍♀️|1426
+🧛🏻‍♀️|1427
+🧛🏼‍♀️|1428
+🧛🏽‍♀️|1429
+🧛🏾‍♀️|1430
+🧛🏿‍♀️|1431
+🧛‍♂️|1420
+🧛🏻‍♂️|1421
+🧛🏼‍♂️|1422
+🧛🏽‍♂️|1423
+🧛🏾‍♂️|1424
+🧛🏿‍♂️|1425
+🧟|1471
+🧟‍♀️|1473
+🧟‍♂️|1472
+🧞|1468
+🧞‍♀️|1470
+🧞‍♂️|1469
+🧜|1432
+🧜🏻|1433
+🧜🏼|1434
+🧜🏽|1435
+🧜🏾|1436
+🧜🏿|1437
+🧜‍♀️|1444
+🧜🏻‍♀️|1445
+🧜🏼‍♀️|1446
+🧜🏽‍♀️|1447
+🧜🏾‍♀️|1448
+🧜🏿‍♀️|1449
+🧜‍♂️|1438
+🧜🏻‍♂️|1439
+🧜🏼‍♂️|1440
+🧜🏽‍♂️|1441
+🧜🏾‍♂️|1442
+🧜🏿‍♂️|1443
+🧚|1396
+🧚🏻|1397
+🧚🏼|1398
+🧚🏽|1399
+🧚🏾|1400
+🧚🏿|1401
+🧚‍♀️|1408
+🧚🏻‍♀️|1409
+🧚🏼‍♀️|1410
+🧚🏽‍♀️|1411
+🧚🏾‍♀️|1412
+🧚🏿‍♀️|1413
+🧚‍♂️|1402
+🧚🏻‍♂️|1403
+🧚🏼‍♂️|1404
+🧚🏽‍♂️|1405
+🧚🏾‍♂️|1406
+🧚🏿‍♂️|1407
+👼|1318
+👼🏻|1319
+👼🏼|1320
+👼🏽|1321
+👼🏾|1322
+👼🏿|1323
+🤰|1276
+🤰🏻|1277
+🤰🏼|1278
+🤰🏽|1279
+🤰🏾|1280
+🤰🏿|1281
+🤱|1294
+🤱🏻|1295
+🤱🏼|1296
+🤱🏽|1297
+🤱🏾|1298
+🤱🏿|1299
+🧑‍🍼|1312
+🧑🏻‍🍼|1313
+🧑🏼‍🍼|1314
+🧑🏽‍🍼|1315
+🧑🏾‍🍼|1316
+🧑🏿‍🍼|1317
+👩‍🍼|1300
+👩🏻‍🍼|1301
+👩🏼‍🍼|1302
+👩🏽‍🍼|1303
+👩🏾‍🍼|1304
+👩🏿‍🍼|1305
+👨‍🍼|1306
+👨🏻‍🍼|1307
+👨🏼‍🍼|1308
+👨🏽‍🍼|1309
+👨🏾‍🍼|1310
+👨🏿‍🍼|1311
+🙇|772
+🙇🏻|773
+🙇🏼|774
+🙇🏽|775
+🙇🏾|776
+🙇🏿|777
+🙇‍♀️|784
+🙇🏻‍♀️|785
+🙇🏼‍♀️|786
+🙇🏽‍♀️|787
+🙇🏾‍♀️|788
+🙇🏿‍♀️|789
+🙇‍♂️|778
+🙇🏻‍♂️|779
+🙇🏼‍♂️|780
+🙇🏽‍♂️|781
+🙇🏾‍♂️|782
+🙇🏿‍♂️|783
+💁|718
+💁🏻|719
+💁🏼|720
+💁🏽|721
+💁🏾|722
+💁🏿|723
+💁‍♀️|730
+💁🏻‍♀️|731
+💁🏼‍♀️|732
+💁🏽‍♀️|733
+💁🏾‍♀️|734
+💁🏿‍♀️|735
+💁‍♂️|724
+💁🏻‍♂️|725
+💁🏼‍♂️|726
+💁🏽‍♂️|727
+💁🏾‍♂️|728
+💁🏿‍♂️|729
+🙅|682
+🙅🏻|683
+🙅🏼|684
+🙅🏽|685
+🙅🏾|686
+🙅🏿|687
+🙅‍♀️|694
+🙅🏻‍♀️|695
+🙅🏼‍♀️|696
+🙅🏽‍♀️|697
+🙅🏾‍♀️|698
+🙅🏿‍♀️|699
+🙅‍♂️|688
+🙅🏻‍♂️|689
+🙅🏼‍♂️|690
+🙅🏽‍♂️|691
+🙅🏾‍♂️|692
+🙅🏿‍♂️|693
+🙆|700
+🙆🏻|701
+🙆🏼|702
+🙆🏽|703
+🙆🏾|704
+🙆🏿|705
+🙆‍♀️|712
+🙆🏻‍♀️|713
+🙆🏼‍♀️|714
+🙆🏽‍♀️|715
+🙆🏾‍♀️|716
+🙆🏿‍♀️|717
+🙆‍♂️|706
+🙆🏻‍♂️|707
+🙆🏼‍♂️|708
+🙆🏽‍♂️|709
+🙆🏾‍♂️|710
+🙆🏿‍♂️|711
+🙋|736
+🙋🏻|737
+🙋🏼|738
+🙋🏽|739
+🙋🏾|740
+🙋🏿|741
+🙋‍♀️|748
+🙋🏻‍♀️|749
+🙋🏼‍♀️|750
+🙋🏽‍♀️|751
+🙋🏾‍♀️|752
+🙋🏿‍♀️|753
+🙋‍♂️|742
+🙋🏻‍♂️|743
+🙋🏼‍♂️|744
+🙋🏽‍♂️|745
+🙋🏾‍♂️|746
+🙋🏿‍♂️|747
+🧏|754
+🧏🏻|755
+🧏🏼|756
+🧏🏽|757
+🧏🏾|758
+🧏🏿|759
+🧏‍♀️|766
+🧏🏻‍♀️|767
+🧏🏼‍♀️|768
+🧏🏽‍♀️|769
+🧏🏾‍♀️|770
+🧏🏿‍♀️|771
+🧏‍♂️|760
+🧏🏻‍♂️|761
+🧏🏼‍♂️|762
+🧏🏽‍♂️|763
+🧏🏾‍♂️|764
+🧏🏿‍♂️|765
+🤦|790
+🤦🏻|791
+🤦🏼|792
+🤦🏽|793
+🤦🏾|794
+🤦🏿|795
+🤦‍♀️|802
+🤦🏻‍♀️|803
+🤦🏼‍♀️|804
+🤦🏽‍♀️|805
+🤦🏾‍♀️|806
+🤦🏿‍♀️|807
+🤦‍♂️|796
+🤦🏻‍♂️|797
+🤦🏼‍♂️|798
+🤦🏽‍♂️|799
+🤦🏾‍♂️|800
+🤦🏿‍♂️|801
+🤷|808
+🤷🏻|809
+🤷🏼|810
+🤷🏽|811
+🤷🏾|812
+🤷🏿|813
+🤷‍♀️|820
+🤷🏻‍♀️|821
+🤷🏼‍♀️|822
+🤷🏽‍♀️|823
+🤷🏾‍♀️|824
+🤷🏿‍♀️|825
+🤷‍♂️|814
+🤷🏻‍♂️|815
+🤷🏼‍♂️|816
+🤷🏽‍♂️|817
+🤷🏾‍♂️|818
+🤷🏿‍♂️|819
+🙎|664
+🙎🏻|665
+🙎🏼|666
+🙎🏽|667
+🙎🏾|668
+🙎🏿|669
+🙎‍♀️|676
+🙎🏻‍♀️|677
+🙎🏼‍♀️|678
+🙎🏽‍♀️|679
+🙎🏾‍♀️|680
+🙎🏿‍♀️|681
+🙎‍♂️|670
+🙎🏻‍♂️|671
+🙎🏼‍♂️|672
+🙎🏽‍♂️|673
+🙎🏾‍♂️|674
+🙎🏿‍♂️|675
+🙍|646
+🙍🏻|647
+🙍🏼|648
+🙍🏽|649
+🙍🏾|650
+🙍🏿|651
+🙍‍♀️|658
+🙍🏻‍♀️|659
+🙍🏼‍♀️|660
+🙍🏽‍♀️|661
+🙍🏾‍♀️|662
+🙍🏿‍♀️|663
+🙍‍♂️|652
+🙍🏻‍♂️|653
+🙍🏼‍♂️|654
+🙍🏽‍♂️|655
+🙍🏾‍♂️|656
+🙍🏿‍♂️|657
+💇|1493
+💇🏻|1494
+💇🏼|1495
+💇🏽|1496
+💇🏾|1497
+💇🏿|1498
+💇‍♀️|1505
+💇🏻‍♀️|1506
+💇🏼‍♀️|1507
+💇🏽‍♀️|1508
+💇🏾‍♀️|1509
+💇🏿‍♀️|1510
+💇‍♂️|1499
+💇🏻‍♂️|1500
+💇🏼‍♂️|1501
+💇🏽‍♂️|1502
+💇🏾‍♂️|1503
+💇🏿‍♂️|1504
+💆|1475
+💆🏻|1476
+💆🏼|1477
+💆🏽|1478
+💆🏾|1479
+💆🏿|1480
+💆‍♀️|1487
+💆🏻‍♀️|1488
+💆🏼‍♀️|1489
+💆🏽‍♀️|1490
+💆🏾‍♀️|1491
+💆🏿‍♀️|1492
+💆‍♂️|1481
+💆🏻‍♂️|1482
+💆🏼‍♂️|1483
+💆🏽‍♂️|1484
+💆🏾‍♂️|1485
+💆🏿‍♂️|1486
+🧖|1658
+🧖🏻|1659
+🧖🏼|1660
+🧖🏽|1661
+🧖🏾|1662
+🧖🏿|1663
+🧖‍♀️|1670
+🧖🏻‍♀️|1671
+🧖🏼‍♀️|1672
+🧖🏽‍♀️|1673
+🧖🏾‍♀️|1674
+🧖🏿‍♀️|1675
+🧖‍♂️|1664
+🧖🏻‍♂️|1665
+🧖🏼‍♂️|1666
+🧖🏽‍♂️|1667
+🧖🏾‍♂️|1668
+🧖🏿‍♂️|1669
+💅|418
+💅🏻|419
+💅🏼|420
+💅🏽|421
+💅🏾|422
+💅🏿|423
+🤳|424
+🤳🏻|425
+🤳🏼|426
+🤳🏽|427
+🤳🏾|428
+🤳🏿|429
+💃|1637
+💃🏻|1638
+💃🏼|1639
+💃🏽|1640
+💃🏾|1641
+💃🏿|1642
+🕺|1643
+🕺🏻|1644
+🕺🏼|1645
+🕺🏽|1646
+🕺🏿|1648
+🕺🏾|1647
+👯|1655
+👯‍♀️|1657
+👯‍♂️|1656
+🕴️|1649
+🕴🏻|1650
+🕴🏼|1651
+🕴🏽|1652
+🕴🏾|1653
+🕴🏿|1654
+🧑‍🦽|1601
+🧑🏻‍🦽|1602
+🧑🏼‍🦽|1603
+🧑🏽‍🦽|1604
+🧑🏾‍🦽|1605
+🧑🏿‍🦽|1606
+👩‍🦽|1613
+👩🏻‍🦽|1614
+👩🏼‍🦽|1615
+👩🏽‍🦽|1616
+👩🏾‍🦽|1617
+👩🏿‍🦽|1618
+👨‍🦽|1607
+👨🏻‍🦽|1608
+👨🏼‍🦽|1609
+👨🏽‍🦽|1610
+👨🏾‍🦽|1611
+👨🏿‍🦽|1612
+🧑‍🦼|1583
+🧑🏻‍🦼|1584
+🧑🏼‍🦼|1585
+🧑🏽‍🦼|1586
+🧑🏾‍🦼|1587
+🧑🏿‍🦼|1588
+👩‍🦼|1595
+👩🏻‍🦼|1596
+👩🏼‍🦼|1597
+👩🏽‍🦼|1598
+👩🏾‍🦼|1599
+👩🏿‍🦼|1600
+👨‍🦼|1589
+👨🏻‍🦼|1590
+👨🏼‍🦼|1591
+👨🏽‍🦼|1592
+👨🏾‍🦼|1593
+👨🏿‍🦼|1594
+🚶|1511
+🚶🏻|1512
+🚶🏼|1513
+🚶🏽|1514
+🚶🏾|1515
+🚶🏿|1516
+🚶‍♀️|1523
+🚶🏻‍♀️|1524
+🚶🏼‍♀️|1525
+🚶🏽‍♀️|1526
+🚶🏾‍♀️|1527
+🚶🏿‍♀️|1528
+🚶‍♂️|1517
+🚶🏻‍♂️|1518
+🚶🏼‍♂️|1519
+🚶🏽‍♂️|1520
+🚶🏾‍♂️|1521
+🚶🏿‍♂️|1522
+🧑‍🦯|1565
+🧑🏻‍🦯|1566
+🧑🏼‍🦯|1567
+🧑🏽‍🦯|1568
+🧑🏾‍🦯|1569
+🧑🏿‍🦯|1570
+👩‍🦯|1577
+👩🏻‍🦯|1578
+👩🏼‍🦯|1579
+👩🏽‍🦯|1580
+👩🏾‍🦯|1581
+👩🏿‍🦯|1582
+👨‍🦯|1571
+👨🏻‍🦯|1572
+👨🏽‍🦯|1574
+👨🏼‍🦯|1573
+👨🏾‍🦯|1575
+👨🏿‍🦯|1576
+🧎|1547
+🧎🏻|1548
+🧎🏼|1549
+🧎🏽|1550
+🧎🏾|1551
+🧎🏿|1552
+🧎‍♀️|1559
+🧎🏻‍♀️|1560
+🧎🏼‍♀️|1561
+🧎🏽‍♀️|1562
+🧎🏾‍♀️|1563
+🧎🏿‍♀️|1564
+🧎‍♂️|1553
+🧎🏻‍♂️|1554
+🧎🏼‍♂️|1555
+🧎🏽‍♂️|1556
+🧎🏾‍♂️|1557
+🧎🏿‍♂️|1558
+🏃|1619
+🏃🏻|1620
+🏃🏼|1621
+🏃🏽|1622
+🏃🏾|1623
+🏃🏿|1624
+🏃‍♀️|1631
+🏃🏻‍♀️|1632
+🏃🏼‍♀️|1633
+🏃🏽‍♀️|1634
+🏃🏾‍♀️|1635
+🏃🏿‍♀️|1636
+🏃‍♂️|1625
+🏃🏻‍♂️|1626
+🏃🏼‍♂️|1627
+🏃🏽‍♂️|1628
+🏃🏾‍♂️|1629
+🏃🏿‍♂️|1630
+🧍|1529
+🧍🏻|1530
+🧍🏼|1531
+🧍🏽|1532
+🧍🏾|1533
+🧍🏿|1534
+🧍‍♀️|1541
+🧍🏻‍♀️|1542
+🧍🏼‍♀️|1543
+🧍🏽‍♀️|1544
+🧍🏾‍♀️|1545
+🧍🏿‍♀️|1546
+🧍‍♂️|1535
+🧍🏻‍♂️|1536
+🧍🏼‍♂️|1537
+🧍🏽‍♂️|1538
+🧍🏾‍♂️|1539
+🧍🏿‍♂️|1540
+🧑‍🤝‍🧑|1957
+🧑🏻‍🤝‍🧑🏻|1958
+🧑🏼‍🤝‍🧑🏼|1964
+🧑🏽‍🤝‍🧑🏽|1970
+🧑🏾‍🤝‍🧑🏾|1976
+🧑🏿‍🤝‍🧑🏿|1982
+👫|2009
+👫🏻|2010
+👫🏼|2016
+👫🏽|2022
+👫🏾|2028
+👫🏿|2034
+👭|1983
+👭🏻|1984
+👭🏼|1990
+👭🏽|1996
+👭🏾|2002
+👭🏿|2008
+👬|2035
+👬🏻|2036
+👬🏼|2042
+👬🏽|2048
+👬🏾|2054
+👬🏿|2060
+💑|2165
+💑🏻|2166
+💑🏼|2167
+💑🏽|2168
+💑🏾|2169
+💑🏿|2170
+👩‍❤️‍👨|2191
+👩🏻‍❤️‍👨🏻|2192
+👩🏼‍❤️‍👨🏼|2198
+👩🏽‍❤️‍👨🏽|2204
+👩🏾‍❤️‍👨🏾|2210
+👩🏿‍❤️‍👨🏿|2216
+👩‍❤️‍👩|2243
+👩🏻‍❤️‍👩🏻|2244
+👩🏼‍❤️‍👩🏼|2250
+👩🏽‍❤️‍👩🏽|2256
+👩🏾‍❤️‍👩🏾|2262
+👩🏿‍❤️‍👩🏿|2268
+👨‍❤️‍👨|2217
+👨🏻‍❤️‍👨🏻|2218
+👨🏼‍❤️‍👨🏼|2224
+👨🏽‍❤️‍👨🏽|2230
+👨🏾‍❤️‍👨🏾|2236
+👨🏿‍❤️‍👨🏿|2242
+💏|2061
+💏🏻|2062
+💏🏼|2063
+💏🏽|2064
+💏🏾|2065
+💏🏿|2066
+👩‍❤️‍💋‍👨|2087
+👩🏻‍❤️‍💋‍👨🏻|2088
+👩🏼‍❤️‍💋‍👨🏼|2094
+👩🏽‍❤️‍💋‍👨🏽|2100
+👩🏾‍❤️‍💋‍👨🏾|2106
+👩🏿‍❤️‍💋‍👨🏿|2112
+👩‍❤️‍💋‍👩|2139
+👩🏻‍❤️‍💋‍👩🏻|2140
+👩🏼‍❤️‍💋‍👩🏼|2146
+👩🏽‍❤️‍💋‍👩🏽|2152
+👩🏾‍❤️‍💋‍👩🏾|2158
+👩🏿‍❤️‍💋‍👩🏿|2164
+👨‍❤️‍💋‍👨|2113
+👨🏻‍❤️‍💋‍👨🏻|2114
+👨🏼‍❤️‍💋‍👨🏼|2120
+👨🏽‍❤️‍💋‍👨🏽|2126
+👨🏾‍❤️‍💋‍👨🏾|2132
+👨🏿‍❤️‍💋‍👨🏿|2138
+👪|2269
+👨‍👩‍👦|2270
+👨‍👩‍👧|2271
+👨‍👩‍👧‍👦|2272
+👨‍👩‍👦‍👦|2273
+👨‍👩‍👧‍👧|2274
+👩‍👩‍👦|2280
+👩‍👩‍👧|2281
+👩‍👩‍👧‍👦|2282
+👩‍👩‍👦‍👦|2283
+👩‍👩‍👧‍👧|2284
+👨‍👨‍👦|2275
+👨‍👨‍👧|2276
+👨‍👨‍👧‍👦|2277
+👨‍👨‍👦‍👦|2278
+👨‍👨‍👧‍👧|2279
+👩‍👦|2290
+👩‍👧|2292
+👩‍👧‍👦|2293
+👩‍👦‍👦|2291
+👩‍👧‍👧|2294
+👨‍👦|2285
+👨‍👧|2287
+👨‍👧‍👦|2288
+👨‍👦‍👦|2286
+👨‍👧‍👧|2289
+🧶|2887
+🧵|2885
+🧥|2899
+🥼|2892
+🦺|2893
+👚|2908
+👕|2895
+👖|2896
+🩲|2905
+🩳|2906
+👔|2894
+👗|2901
+👙|2907
+🩱|2904
+👘|2902
+🥻|2903
+🥿|2918
+👠|2919
+👡|2920
+👢|2922
+👞|2915
+👟|2916
+🥾|2917
+🩴|2914
+🧦|2900
+🧤|2898
+🧣|2897
+🎩|2925
+🧢|2927
+👒|2924
+🎓|2926
+⛑️|2929
+🪖|2928
+👑|2923
+💍|2932
+👝|2911
+👛|2909
+👜|2910
+💼|3044
+🎒|2913
+🧳|2724
+👓|2889
+🕶️|2890
+🥽|2891
+🌂|2791
+🐶|2313
+🐱|2321
+🐭|2353
+🐹|2356
+🐰|2357
+🦊|2319
+🐻|2363
+🐼|2366
+🐻‍❄️|2364
+🐨|2365
+🐯|2325
+🦁|2324
+🐮|2334
+🐷|2338
+🐽|2341
+🐸|2391
+🐵|2309
+🙈|124
+🙉|125
+🙊|126
+🐒|2310
+🐔|2374
+🐧|2380
+🐦|2379
+🐤|2377
+🐣|2376
+🐥|2378
+🦆|2383
+🦤|2386
+🦅|2382
+🦉|2385
+🦇|2362
+🐺|2318
+🐗|2340
+🐴|2328
+🦄|2330
+🐝|2415
+🐛|2413
+🦋|2412
+🐌|2411
+🪱|2425
+🐞|2417
+🐜|2414
+🪰|2424
+🦟|2423
+🪳|2419
+🪲|2416
+🦗|2418
+🕷️|2420
+🕸️|2421
+🦂|2422
+🐢|2393
+🐍|2395
+🦎|2394
+🦖|2399
+🦕|2398
+🐙|2408
+🦑|2542
+🦐|2541
+🦞|2540
+🦀|2539
+🐡|2406
+🐠|2405
+🐟|2404
+🦭|2403
+🐬|2402
+🐳|2400
+🐋|2401
+🦈|2407
+🐊|2392
+🐅|2326
+🐆|2327
+🦓|2331
+🦍|2311
+🦧|2312
+🐘|2349
+🦣|2350
+🦬|2333
+🦛|2352
+🦏|2351
+🐪|2345
+🐫|2346
+🦒|2348
+🦘|2370
+🐃|2336
+🐂|2335
+🐄|2337
+🐎|2329
+🐖|2339
+🐏|2342
+🐑|2343
+🦙|2347
+🐐|2344
+🦌|2332
+🐕|2314
+🐩|2317
+🦮|2315
+🐕‍🦺|2316
+🐈|2322
+🐈‍⬛|2323
+🐓|2375
+🦃|2373
+🦚|2389
+🦜|2390
+🦢|2384
+🦩|2388
+🕊️|2381
+🐇|2358
+🦝|2320
+🦨|2369
+🦡|2371
+🦫|2360
+🦦|2368
+🦥|2367
+🐁|2354
+🐀|2355
+🐿️|2359
+🦔|2361
+🐾|2372
+🐉|2397
+🐲|2396
+🌵|2443
+🎄|2804
+🌲|2440
+🌳|2441
+🌴|2442
+🌱|2438
+🌿|2445
+🍀|2447
+🎍|2813
+🎋|2812
+🍃|2450
+🍂|2449
+🍁|2448
+🪶|2387
+🍄|2484
+🐚|2409
+🪨|2605
+🪵|2606
+🌾|2444
+🪴|2439
+💐|2427
+🌷|2437
+🌹|2432
+🥀|2433
+🌺|2434
+🌸|2428
+🌼|2436
+🌻|2435
+🌞|2771
+🌝|2770
+🌛|2766
+🌜|2767
+🌚|2765
+🌕|2760
+🌖|2761
+🌗|2762
+🌘|2763
+🌑|2756
+🌒|2757
+🌓|2758
+🌔|2759
+🌙|2764
+🌎|2586
+🌍|2585
+🌏|2587
+🪐|2772
+💫|153
+⭐|2773
+🌟|2774
+⚡|2795
+💥|152
+🔥|2800
+🌪️|2786
+🌈|2790
+🌤️|2780
+⛅|2778
+🌥️|2781
+🌦️|2782
+🌧️|2783
+⛈️|2779
+🌩️|2785
+🌨️|2784
+⛄|2798
+🌬️|2788
+💨|155
+💧|2801
+💦|154
+🌊|2802
+🌫️|2787
+🍏|2462
+🍎|2461
+🍐|2463
+🍊|2456
+🍋|2457
+🍌|2458
+🍉|2455
+🍇|2453
+🫐|2467
+🍓|2466
+🍈|2454
+🍒|2465
+🍑|2464
+🥭|2460
+🍍|2459
+🥥|2471
+🥝|2468
+🍅|2469
+🍆|2473
+🥑|2472
+🫒|2470
+🥦|2481
+🥬|2480
+🫑|2478
+🥒|2479
+🌶️|2477
+🌽|2476
+🥕|2475
+🧄|2482
+🧅|2483
+🥔|2474
+🍠|2529
+🥐|2489
+🥯|2493
+🍞|2488
+🥖|2490
+🫓|2491
+🥨|2492
+🧀|2496
+🥚|2511
+🍳|2512
+🧈|2519
+🥞|2494
+🧇|2495
+🥓|2500
+🥩|2499
+🍗|2498
+🍖|2497
+🌭|2504
+🍔|2501
+🍟|2502
+🍕|2503
+🥪|2505
+🥙|2509
+🧆|2510
+🌮|2506
+🌯|2507
+🫔|2508
+🥗|2517
+🥘|2513
+🫕|2515
+🥫|2521
+🍝|2528
+🍜|2527
+🍲|2514
+🍛|2526
+🍣|2531
+🍱|2522
+🥟|2536
+🦪|2543
+🍤|2532
+🍙|2524
+🍚|2525
+🍘|2523
+🍥|2533
+🥠|2537
+🥮|2534
+🍢|2530
+🍡|2535
+🍧|2545
+🍨|2546
+🍦|2544
+🥧|2552
+🧁|2551
+🍰|2550
+🎂|2549
+🍮|2556
+🍭|2555
+🍬|2554
+🍫|2553
+🍿|2518
+🍩|2547
+🍪|2548
+🌰|2487
+🥜|2485
+🍯|2557
+🥛|2559
+🍼|2558
+🍵|2562
+🫖|2561
+🧉|2576
+🧋|2574
+🧃|2575
+🥤|2573
+🍶|2563
+🍺|2568
+🍻|2569
+🥂|2570
+🍷|2565
+🥃|2571
+🍸|2566
+🍹|2567
+🍾|2564
+🧊|2577
+🥄|2581
+🍴|2580
+🍽️|2579
+🥣|2516
+🥡|2538
+🥢|2578
+🧂|2520
+⚽|2830
+🏀|2833
+🏈|2835
+⚾|2831
+🥎|2832
+🎾|2837
+🏐|2834
+🏉|2836
+🥏|2838
+🪃|3081
+🎱|2860
+🪀|2858
+🏓|2844
+🏸|2845
+🏒|2842
+🏑|2841
+🥍|2843
+🏏|2840
+🥅|2848
+⛳|2849
+🪁|2859
+🏹|3082
+🎣|2851
+🤿|2852
+🥊|2846
+🥋|2847
+🎽|2853
+🛹|2688
+🛼|2689
+🛷|2855
+⛸️|2850
+🥌|2856
+🎿|2854
+⛷️|1701
+🏂|1702
+🪂|2714
+🏋|1798
+🏋🏻|1799
+🏋🏼|1800
+🏋🏽|1801
+🏋🏾|1802
+🏋🏿|1803
+🏋️‍♀️|1810
+🏋🏻‍♀️|1811
+🏋🏼‍♀️|1812
+🏋🏽‍♀️|1813
+🏋🏾‍♀️|1814
+🏋🏿‍♀️|1815
+🏋️‍♂️|1804
+🏋🏻‍♂️|1805
+🏋🏼‍♂️|1806
+🏋🏽‍♂️|1807
+🏋🏾‍♂️|1808
+🏋🏿‍♂️|1809
+🤼|1870
+🤼‍♀️|1872
+🤼‍♂️|1871
+🤸|1852
+🤸🏻|1853
+🤸🏼|1854
+🤸🏽|1855
+🤸🏾|1856
+🤸🏿|1857
+🤸‍♀️|1864
+🤸🏻‍♀️|1865
+🤸🏼‍♀️|1866
+🤸🏽‍♀️|1867
+🤸🏾‍♀️|1868
+🤸🏿‍♀️|1869
+🤸‍♂️|1858
+🤸🏻‍♂️|1859
+🤸🏼‍♂️|1860
+🤸🏽‍♂️|1861
+🤸🏾‍♂️|1862
+🤸🏿‍♂️|1863
+⛹|1780
+⛹🏻|1781
+⛹🏼|1782
+⛹🏽|1783
+⛹🏾|1784
+⛹🏿|1785
+⛹️‍♀️|1792
+⛹🏻‍♀️|1793
+⛹🏼‍♀️|1794
+⛹🏽‍♀️|1795
+⛹🏾‍♀️|1796
+⛹🏿‍♀️|1797
+⛹️‍♂️|1786
+⛹🏻‍♂️|1787
+⛹🏼‍♂️|1788
+⛹🏽‍♂️|1789
+⛹🏾‍♂️|1790
+⛹🏿‍♂️|1791
+🤺|1694
+🤾|1891
+🤾🏻|1892
+🤾🏼|1893
+🤾🏽|1894
+🤾🏾|1895
+🤾🏿|1896
+🤾‍♀️|1903
+🤾🏻‍♀️|1904
+🤾🏼‍♀️|1905
+🤾🏽‍♀️|1906
+🤾🏾‍♀️|1907
+🤾🏿‍♀️|1908
+🤾‍♂️|1897
+🤾🏻‍♂️|1898
+🤾🏼‍♂️|1899
+🤾🏽‍♂️|1900
+🤾🏾‍♂️|1901
+🤾🏿‍♂️|1902
+🏌|1708
+🏌🏻|1709
+🏌🏼|1710
+🏌🏽|1711
+🏌🏾|1712
+🏌🏿|1713
+🏌️‍♀️|1720
+🏌🏻‍♀️|1721
+🏌🏼‍♀️|1722
+🏌🏽‍♀️|1723
+🏌🏾‍♀️|1724
+🏌🏿‍♀️|1725
+🏌️‍♂️|1714
+🏌🏻‍♂️|1715
+🏌🏼‍♂️|1716
+🏌🏽‍♂️|1717
+🏌🏾‍♂️|1718
+🏌🏿‍♂️|1719
+🏇|1695
+🏇🏻|1696
+🏇🏼|1697
+🏇🏽|1698
+🏇🏾|1699
+🏇🏿|1700
+🧘|1927
+🧘🏻|1928
+🧘🏼|1929
+🧘🏽|1930
+🧘🏾|1931
+🧘🏿|1932
+🧘‍♀️|1939
+🧘🏻‍♀️|1940
+🧘🏼‍♀️|1941
+🧘🏽‍♀️|1942
+🧘🏾‍♀️|1943
+🧘🏿‍♀️|1944
+🧘‍♂️|1933
+🧘🏻‍♂️|1934
+🧘🏼‍♂️|1935
+🧘🏽‍♂️|1936
+🧘🏾‍♂️|1937
+🧘🏿‍♂️|1938
+🏄|1726
+🏄🏻|1727
+🏄🏼|1728
+🏄🏽|1729
+🏄🏾|1730
+🏄🏿|1731
+🏄‍♀️|1738
+🏄🏻‍♀️|1739
+🏄🏼‍♀️|1740
+🏄🏽‍♀️|1741
+🏄🏾‍♀️|1742
+🏄🏿‍♀️|1743
+🏄‍♂️|1732
+🏄🏻‍♂️|1733
+🏄🏼‍♂️|1734
+🏄🏽‍♂️|1735
+🏄🏾‍♂️|1736
+🏄🏿‍♂️|1737
+🏊|1762
+🏊🏻|1763
+🏊🏼|1764
+🏊🏽|1765
+🏊🏾|1766
+🏊🏿|1767
+🏊‍♀️|1774
+🏊🏻‍♀️|1775
+🏊🏼‍♀️|1776
+🏊🏽‍♀️|1777
+🏊🏾‍♀️|1778
+🏊🏿‍♀️|1779
+🏊‍♂️|1768
+🏊🏻‍♂️|1769
+🏊🏼‍♂️|1770
+🏊🏽‍♂️|1771
+🏊🏾‍♂️|1772
+🏊🏿‍♂️|1773
+🤽|1873
+🤽🏻|1874
+🤽🏼|1875
+🤽🏽|1876
+🤽🏾|1877
+🤽🏿|1878
+🤽‍♀️|1885
+🤽🏻‍♀️|1886
+🤽🏼‍♀️|1887
+🤽🏽‍♀️|1888
+🤽🏾‍♀️|1889
+🤽🏿‍♀️|1890
+🤽‍♂️|1879
+🤽🏻‍♂️|1880
+🤽🏼‍♂️|1881
+🤽🏽‍♂️|1882
+🤽🏾‍♂️|1883
+🤽🏿‍♂️|1884
+🚣|1744
+🚣🏻|1745
+🚣🏼|1746
+🚣🏽|1747
+🚣🏾|1748
+🚣🏿|1749
+🚣‍♀️|1756
+🚣🏻‍♀️|1757
+🚣🏼‍♀️|1758
+🚣🏽‍♀️|1759
+🚣🏾‍♀️|1760
+🚣🏿‍♀️|1761
+🚣‍♂️|1750
+🚣🏻‍♂️|1751
+🚣🏼‍♂️|1752
+🚣🏽‍♂️|1753
+🚣🏾‍♂️|1754
+🚣🏿‍♂️|1755
+🧗|1676
+🧗🏻|1677
+🧗🏼|1678
+🧗🏽|1679
+🧗🏾|1680
+🧗🏿|1681
+🧗‍♀️|1688
+🧗🏻‍♀️|1689
+🧗🏼‍♀️|1690
+🧗🏽‍♀️|1691
+🧗🏾‍♀️|1692
+🧗🏿‍♀️|1693
+🧗‍♂️|1682
+🧗🏻‍♂️|1683
+🧗🏼‍♂️|1684
+🧗🏽‍♂️|1685
+🧗🏾‍♂️|1686
+🧗🏿‍♂️|1687
+🚵|1834
+🚵🏻|1835
+🚵🏼|1836
+🚵🏽|1837
+🚵🏾|1838
+🚵🏿|1839
+🚵‍♀️|1846
+🚵🏻‍♀️|1847
+🚵🏼‍♀️|1848
+🚵🏽‍♀️|1849
+🚵🏾‍♀️|1850
+🚵🏿‍♀️|1851
+🚵‍♂️|1840
+🚵🏻‍♂️|1841
+🚵🏼‍♂️|1842
+🚵🏽‍♂️|1843
+🚵🏾‍♂️|1844
+🚵🏿‍♂️|1845
+🚴|1816
+🚴🏻|1817
+🚴🏼|1818
+🚴🏽|1819
+🚴🏾|1820
+🚴🏿|1821
+🚴‍♀️|1828
+🚴🏻‍♀️|1829
+🚴🏼‍♀️|1830
+🚴🏽‍♀️|1831
+🚴🏾‍♀️|1832
+🚴🏿‍♀️|1833
+🚴‍♂️|1822
+🚴🏻‍♂️|1823
+🚴🏼‍♂️|1824
+🚴🏽‍♂️|1825
+🚴🏾‍♂️|1826
+🚴🏿‍♂️|1827
+🏆|2825
+🥇|2827
+🥈|2828
+🥉|2829
+🏅|2826
+🎖️|2824
+🏵️|2431
+🎗️|2821
+🎫|2823
+🎟️|2822
+🎪|2650
+🤹|1909
+🤹🏻|1910
+🤹🏼|1911
+🤹🏽|1912
+🤹🏾|1913
+🤹🏿|1914
+🤹‍♀️|1921
+🤹🏻‍♀️|1922
+🤹🏼‍♀️|1923
+🤹🏽‍♀️|1924
+🤹🏾‍♀️|1925
+🤹🏿‍♀️|1926
+🤹‍♂️|1915
+🤹🏻‍♂️|1916
+🤹🏼‍♂️|1917
+🤹🏽‍♂️|1918
+🤹🏾‍♂️|1919
+🤹🏿‍♂️|1920
+🎭|2882
+🩰|2921
+🎨|2884
+🎬|2984
+🎤|2949
+🎧|2950
+🎼|2943
+🎹|2955
+🥁|2959
+🪘|2960
+🎷|2952
+🎺|2956
+🎸|2954
+🪕|2958
+🎻|2957
+🪗|2953
+🎲|2868
+♟️|2878
+🎯|2857
+🎳|2839
+🎮|2865
+🎰|2867
+🧩|2869
+🚗|2673
+🚕|2671
+🚙|2675
+🛻|2676
+🚌|2663
+🚎|2665
+🏎️|2680
+🚓|2669
+🚑|2667
+🚒|2668
+🚐|2666
+🚚|2677
+🚛|2678
+🚜|2679
+🦯|3091
+🦽|2683
+🦼|2684
+🛴|2687
+🚲|2686
+🛵|2682
+🏍️|2681
+🛺|2685
+🚨|2696
+🚔|2670
+🚍|2664
+🚘|2674
+🚖|2672
+🚡|2719
+🚠|2718
+🚟|2717
+🚃|2652
+🚋|2662
+🚞|2661
+🚝|2660
+🚄|2653
+🚅|2654
+🚈|2657
+🚂|2651
+🚆|2655
+🚇|2656
+🚊|2659
+🚉|2658
+🛫|2712
+🛬|2713
+🛩️|2711
+💺|2715
+🛰️|2720
+🚀|2721
+🛸|2722
+🚁|2716
+🛶|2704
+⛵|2703
+🚤|2705
+🛥️|2708
+🛳️|2706
+⛴️|2707
+🚢|2709
+⛽|2694
+🚧|2700
+🚦|2698
+🚥|2697
+🚏|2690
+🗺️|2589
+🗿|3141
+🗽|2627
+🗼|2626
+🏰|2624
+🏯|2623
+🏟️|2601
+🎡|2647
+🎢|2648
+🎠|2645
+⛲|2634
+⛱️|2794
+🏖️|2597
+🏝️|2599
+🏜️|2598
+🌋|2594
+⛰️|2593
+🏔️|2592
+🗻|2595
+🏕️|2596
+⛺|2635
+🏠|2610
+🏡|2611
+🏘️|2608
+🏚️|2609
+🛖|2607
+🏗️|2603
+🏭|2622
+🏢|2612
+🏬|2621
+🏣|2613
+🏤|2614
+🏥|2615
+🏦|2616
+🏨|2617
+🏪|2619
+🏫|2620
+🏩|2618
+💒|2625
+🏛️|2602
+⛪|2628
+🕌|2629
+🕍|2631
+🛕|2630
+🕋|2633
+⛩️|2632
+🛤️|2692
+🛣️|2691
+🗾|2590
+🎑|2817
+🏞️|2600
+🌅|2640
+🌄|2639
+🌠|2775
+🎇|2806
+🎆|2805
+🌇|2642
+🌆|2641
+🏙️|2638
+🌃|2637
+🌌|2776
+🌉|2643
+🌁|2636
+⌚|2727
+📱|2961
+📲|2962
+💻|2970
+🖥️|2971
+🖨️|2972
+🖱️|2974
+🖲️|2975
+🕹️|2866
+🗜️|3089
+💽|2976
+💾|2977
+💿|2978
+📀|2979
+📼|2989
+📷|2986
+📸|2987
+📹|2988
+🎥|2981
+📽️|2983
+🎞️|2982
+📞|2964
+☎️|2963
+📟|2965
+📠|2966
+📺|2985
+📻|2951
+🎙️|2946
+🎚️|2947
+🎛️|2948
+🧭|2591
+⏱️|2729
+⏲️|2730
+⏰|2728
+🕰️|2731
+⌛|2725
+⏳|2726
+📡|3104
+🔋|2967
+🔌|2969
+💡|2993
+🔦|2994
+🕯️|2992
+🪔|2996
+🧯|3135
+🛢️|2693
+💸|3020
+💵|3017
+💴|3016
+💶|3018
+💷|3019
+🪙|3015
+💰|3014
+💳|3021
+💎|2933
+🪜|3097
+🧰|3095
+🪛|3086
+🔧|3085
+🔨|3073
+🛠️|3077
+⛏️|3075
+🔩|3087
+🧱|2604
+⛓️|3093
+🪝|3094
+🪢|2888
+🧲|3096
+🔫|3080
+💣|157
+🧨|2807
+🪓|3074
+🪚|3084
+🔪|2582
+🗡️|3078
+🛡️|3083
+🚬|3137
+⚰️|3138
+🪦|3139
+⚱️|3140
+🏺|2584
+🪄|2862
+🔮|2861
+📿|2930
+🧿|2863
+💈|2649
+🔭|3103
+🔬|3102
+🕳️|156
+🪟|3115
+🩹|3108
+🩺|3110
+💊|3107
+💉|3105
+🩸|3106
+🧬|3101
+🦠|2426
+🧫|3100
+🧪|3099
+🌡️|2768
+🪤|3123
+🧹|3127
+🧺|3128
+🪡|2886
+🧻|3129
+🚽|3119
+🪠|3120
+🪣|3130
+🚰|3146
+🚿|3121
+🛁|3122
+🛀|1945
+🛀🏻|1946
+🛀🏼|1947
+🛀🏽|1948
+🛀🏾|1949
+🛀🏿|1950
+🪥|3133
+🧼|3131
+🪒|3124
+🧽|3134
+🧴|3125
+🛎️|2723
+🔑|3071
+🗝️|3072
+🚪|3112
+🪑|3118
+🪞|3114
+🛋️|3117
+🛏️|3116
+🛌|1951
+🧸|2870
+🖼️|2883
+🛍️|2912
+🛒|3136
+🎁|2820
+🎈|2809
+🎏|2815
+🎀|2819
+🎊|2811
+🎉|2810
+🪅|2871
+🪆|2873
+🎎|2814
+🏮|2995
+🎐|2816
+🧧|2818
+📩|3027
+📨|3026
+📧|3025
+💌|128
+📥|3029
+📤|3028
+📦|3030
+🏷️|3013
+📪|3032
+📫|3031
+📬|3033
+📭|3034
+📮|3035
+📯|2940
+🪧|3142
+📜|3007
+📃|3006
+📄|3008
+📑|3011
+🧾|3022
+📊|3055
+📈|3053
+📉|3054
+🗒️|3050
+🗓️|3051
+📆|3049
+📅|3048
+🗑️|3066
+📇|3052
+🗃️|3064
+🗳️|3036
+🗄️|3065
+📋|3056
+📁|3045
+📂|3046
+🗂️|3047
+🗞️|3010
+📰|3009
+📓|3004
+📔|2997
+📒|3005
+📕|2998
+📗|3000
+📘|3001
+📙|3002
+📚|3003
+📖|2999
+🔖|3012
+🧷|3126
+🔗|3092
+📎|3059
+🖇️|3060
+📐|3062
+📏|3061
+🧮|2980
+📌|3057
+📍|3058
+🖊️|3040
+🖋️|3039
+🖌️|3041
+🖍️|3042
+📝|3043
+✏️|3037
+🔍|2990
+🔎|2991
+🔏|3069
+🔐|3070
+🔒|3067
+🔓|3068
+🧡|142
+💛|143
+💚|144
+💙|145
+💜|146
+🖤|148
+🤎|147
+🤍|149
+💔|138
+💕|135
+💞|134
+💓|133
+💗|132
+💖|131
+💘|129
+💝|130
+❤️‍🩹|140
+❤️‍🔥|139
+💟|136
+☮️|3200
+✝️|3197
+☪️|3199
+🕉️|3193
+🔯|3202
+🕎|3201
+☯️|3196
+🛐|3191
+⛎|3215
+♊|3205
+♋|3206
+♌|3207
+♍|3208
+♎|3209
+♏|3210
+🆔|3304
+⚛️|3192
+🉑|3323
+📴|3239
+📳|3238
+🈶|3317
+🈚|3321
+🈸|3324
+🈺|3329
+🈷️|3316
+🆚|3313
+💮|2429
+🉐|3319
+🈴|3325
+🈵|3330
+🈹|3320
+🈲|3322
+🅰️|3297
+🅱️|3299
+🆎|3298
+🆑|3300
+🅾️|3308
+🆘|3311
+❌|3268
+⭕|3264
+🛑|2699
+⛔|3159
+📛|3262
+🚫|3160
+💯|150
+💢|151
+🚷|3165
+🚯|3163
+🚳|3161
+🚱|3164
+🔞|3167
+📵|3166
+🚭|3162
+‼️|3249
+🔅|3235
+🔆|3236
+〽️|3272
+⚠️|3157
+🚸|3158
+🔱|3261
+⚜️|3260
+🔰|3263
+♻️|3259
+🈯|3318
+💹|3023
+❎|3269
+🌐|2588
+💠|3361
+Ⓜ️|3305
+🌀|2789
+💤|163
+🏧|3144
+🚾|3152
+♿|3147
+🅿️|3310
+🈳|3326
+🈂️|3315
+🛂|3153
+🛃|3154
+🛄|3155
+🛅|3156
+🛗|3113
+🚹|3148
+🚺|3149
+🚼|3151
+🚻|3150
+🚮|3145
+🎦|3234
+📶|3237
+🈁|3314
+🔣|3295
+🔤|3296
+🔡|3293
+🔠|3292
+🆖|3307
+🆗|3309
+🆙|3312
+🆒|3301
+🆕|3306
+🆓|3302
+0️⃣|3281
+1️⃣|3282
+2️⃣|3283
+3️⃣|3284
+4️⃣|3285
+5️⃣|3286
+6️⃣|3287
+7️⃣|3288
+8️⃣|3289
+9️⃣|3290
+🔟|3291
+🔢|3294
+#️⃣|3279
+*️⃣|3280
+⏏️|3233
+▶️|3219
+⏸️|3230
+⏯️|3222
+⏹️|3231
+⏺️|3232
+⏭️|3221
+⏮️|3225
+⏩|3220
+⏪|3224
+⏫|3227
+⏬|3229
+◀️|3223
+🔼|3226
+🔽|3228
+➡️|3172
+⬅️|3176
+⬆️|3170
+⬇️|3174
+↪️|3181
+↩️|3180
+🔀|3216
+🔁|3217
+🔂|3218
+🔄|3185
+🔃|3184
+🎵|2944
+🎶|2945
+♾️|3248
+💲|3257
+💱|3256
+©️|3276
+®️|3277
+➰|3270
+➿|3271
+🔚|3187
+🔙|3186
+🔛|3188
+🔝|3190
+🔜|3189
+🔘|3362
+⚪|3339
+⚫|3338
+🔴|3331
+🔵|3335
+🟤|3337
+🟣|3336
+🟢|3334
+🟡|3333
+🟠|3332
+🔺|3359
+🔻|3360
+🔸|3357
+🔹|3358
+🔶|3355
+🔷|3356
+🔳|3363
+🔲|3364
+▪️|3353
+▫️|3354
+◾|3351
+◽|3352
+◼️|3349
+◻️|3350
+⬛|3347
+⬜|3348
+🟧|3341
+🟦|3344
+🟥|3340
+🟫|3346
+🟪|3345
+🟩|3343
+🟨|3342
+🔈|2935
+🔇|2934
+🔉|2936
+🔊|2937
+🔔|2941
+🔕|2942
+📣|2939
+📢|2938
+🗨️|160
+👁️‍🗨️|159
+💬|158
+💭|162
+🗯️|161
+🃏|2879
+🎴|2881
+🀄|2880
+🕐|2734
+🕑|2736
+🕒|2738
+🕓|2740
+🕔|2742
+🕕|2744
+🕖|2746
+🕗|2748
+🕘|2750
+🕙|2752
+🕚|2754
+🕛|2732
+🕜|2735
+🕝|2737
+🕞|2739
+🕟|2741
+🕠|2743
+🕡|2745
+🕢|2747
+🕣|2749
+🕤|2751
+🕥|2753
+🕦|2755
+🕧|2733
+⚧|3242
+🏳️|3369
+🏴|3368
+🏁|3365
+🚩|3366
+🏳️‍🌈|3370
+🏳️‍⚧️|3371
+🏴‍☠️|3372
+🇦🇫|3376
+🇦🇽|3388
+🇦🇱|3379
+🇩🇿|3437
+🇦🇸|3384
+🇦🇩|3374
+🇦🇴|3381
+🇦🇮|3378
+🇦🇶|3382
+🇦🇬|3377
+🇦🇷|3383
+🇦🇲|3380
+🇦🇼|3387
+🇦🇺|3386
+🇦🇹|3385
+🇦🇿|3389
+🇧🇸|3405
+🇧🇭|3396
+🇧🇩|3392
+🇧🇧|3391
+🇧🇾|3409
+🇧🇪|3393
+🇧🇿|3410
+🇧🇯|3398
+🇧🇲|3400
+🇧🇹|3406
+🇧🇴|3402
+🇧🇦|3390
+🇧🇼|3408
+🇧🇷|3404
+🇮🇴|3484
+🇻🇬|3619
+🇧🇳|3401
+🇧🇬|3395
+🇧🇫|3394
+🇧🇮|3397
+🇰🇭|3495
+🇨🇲|3420
+🇨🇦|3411
+🇮🇨|3478
+🇨🇻|3426
+🇧🇶|3403
+🇰🇾|3502
+🇨🇫|3414
+🇹🇩|3594
+🇨🇱|3419
+🇨🇳|3421
+🇨🇽|3428
+🇨🇨|3412
+🇨🇴|3422
+🇰🇲|3497
+🇨🇬|3415
+🇨🇩|3413
+🇨🇰|3418
+🇨🇷|3424
+🇨🇮|3417
+🇭🇷|3475
+🇨🇺|3425
+🇨🇼|3427
+🇨🇾|3429
+🇨🇿|3430
+🇩🇰|3434
+🇩🇯|3433
+🇩🇲|3435
+🇩🇴|3436
+🇪🇨|3439
+🇪🇬|3441
+🇸🇻|3588
+🇬🇶|3465
+🇪🇷|3443
+🇪🇪|3440
+🇪🇹|3445
+🇪🇺|3446
+🇫🇰|3449
+🇫🇴|3451
+🇫🇯|3448
+🇫🇮|3447
+🇫🇷|3452
+🇬🇫|3457
+🇵🇫|3553
+🇹🇫|3595
+🇬🇦|3453
+🇬🇲|3462
+🇬🇪|3456
+🇩🇪|3431
+🇬🇭|3459
+🇬🇮|3460
+🇬🇷|3466
+🇬🇱|3461
+🇬🇩|3455
+🇬🇵|3464
+🇬🇺|3469
+🇬🇹|3468
+🇬🇬|3458
+🇬🇳|3463
+🇬🇼|3470
+🇬🇾|3471
+🇭🇹|3476
+🇭🇳|3474
+🇭🇰|3472
+🇭🇺|3477
+🇮🇸|3487
+🇮🇳|3483
+🇮🇩|3479
+🇮🇷|3486
+🇮🇶|3485
+🇮🇪|3480
+🇮🇲|3482
+🇮🇱|3481
+🇮🇹|3488
+🇯🇲|3490
+🇯🇵|3492
+🎌|3367
+🇯🇪|3489
+🇯🇴|3491
+🇰🇿|3503
+🇰🇪|3493
+🇰🇮|3496
+🇽🇰|3625
+🇰🇼|3501
+🇰🇬|3494
+🇱🇦|3504
+🇱🇻|3513
+🇱🇧|3505
+🇱🇸|3510
+🇱🇷|3509
+🇱🇾|3514
+🇱🇮|3507
+🇱🇹|3511
+🇱🇺|3512
+🇲🇴|3526
+🇲🇰|3522
+🇲🇬|3520
+🇲🇼|3534
+🇲🇾|3536
+🇲🇻|3533
+🇲🇱|3523
+🇲🇹|3531
+🇲🇭|3521
+🇲🇶|3528
+🇲🇷|3529
+🇲🇺|3532
+🇾🇹|3627
+🇲🇽|3535
+🇫🇲|3450
+🇲🇩|3517
+🇲🇨|3516
+🇲🇳|3525
+🇲🇪|3518
+🇲🇸|3530
+🇲🇦|3515
+🇲🇿|3537
+🇲🇲|3524
+🇳🇦|3538
+🇳🇷|3547
+🇳🇵|3546
+🇳🇱|3544
+🇳🇨|3539
+🇳🇿|3549
+🇳🇮|3543
+🇳🇪|3540
+🇳🇬|3542
+🇳🇺|3548
+🇳🇫|3541
+🇰🇵|3499
+🇲🇵|3527
+🇳🇴|3545
+🇴🇲|3550
+🇵🇰|3556
+🇵🇼|3563
+🇵🇸|3561
+🇵🇦|3551
+🇵🇬|3554
+🇵🇾|3564
+🇵🇪|3552
+🇵🇭|3555
+🇵🇳|3559
+🇵🇱|3557
+🇵🇹|3562
+🇵🇷|3560
+🇶🇦|3565
+🇷🇪|3566
+🇷🇴|3567
+🇷🇺|3569
+🇷🇼|3570
+🇼🇸|3624
+🇸🇲|3582
+🇸🇹|3587
+🇸🇦|3571
+🇸🇳|3583
+🇷🇸|3568
+🇸🇨|3573
+🇸🇱|3581
+🇸🇬|3576
+🇸🇽|3589
+🇸🇰|3580
+🇸🇮|3578
+🇬🇸|3467
+🇸🇧|3572
+🇸🇴|3584
+🇿🇦|3628
+🇰🇷|3500
+🇸🇸|3586
+🇪🇸|3444
+🇱🇰|3508
+🇧🇱|3399
+🇸🇭|3577
+🇰🇳|3498
+🇱🇨|3506
+🇵🇲|3558
+🇻🇨|3617
+🇸🇩|3574
+🇸🇷|3585
+🇸🇿|3591
+🇸🇪|3575
+🇨🇭|3416
+🇸🇾|3590
+🇹🇼|3607
+🇹🇯|3598
+🇹🇿|3608
+🇹🇭|3597
+🇹🇱|3600
+🇹🇬|3596
+🇹🇰|3599
+🇹🇴|3603
+🇹🇹|3605
+🇹🇳|3602
+🇹🇷|3604
+🇹🇲|3601
+🇹🇨|3593
+🇻🇮|3620
+🇹🇻|3606
+🇺🇬|3610
+🇺🇦|3609
+🇦🇪|3375
+🇬🇧|3454
+🏴󠁧󠁢󠁥󠁮󠁧󠁿|3631
+🏴󠁧󠁢󠁳󠁣󠁴󠁿|3632
+🏴󠁧󠁢󠁷󠁬󠁳󠁿|3633
+🇺🇸|3613
+🇺🇾|3614
+🇺🇿|3615
+🇻🇺|3622
+🇻🇦|3616
+🇻🇪|3618
+🇻🇳|3621
+🇼🇫|3623
+🇪🇭|3442
+🇾🇪|3626
+🇿🇲|3629
+🇿🇼|3630

+ 37 - 0
app/assets/emojis/search-index/pl.csv

@@ -66,6 +66,43 @@
 〰️|fala|falista kreska|linia|pofalowana
 ㊗️|gratulacje|ideogram|japoński przycisk gratulacje
 ㊙️|ideogram|japoński przycisk tajemnica|tajemnica
+🥹|duma|opór|płacz|smutna twarz|twarz powstrzymująca łzy|zdenerwowanie
+🧌|bajka|fantazja|potwór|troll
+🩻|kości|lekarz|medycyna|szkielet|zdjęcie rentgenowskie
+🩼|ból|kij|kula|laska|niepełnosprawność|pomoc w chodzeniu
+🪩|dyskoteka|kula dyskotekowa|migotanie|przyjęcie|taniec
+🪪|bezpieczeństwo|dane osobowe|dowód tożsamości|prawo jazdy
+🪫|elektronika|niski poziom energii|słaba bateria
+🪬|amulet|chamsa|dłoń fatimy|dłoń z okiem|fatima|miriam|ochrona|ręka fatimy|ręka miriam|ręka z okiem
+🪷|buddyzm|czystość|hinduizm|indie|kwiat|lotosu|wietnam
+🪸|koralowa|koralowiec|ocean|rafa
+🪹|gniazdowanie|gnieżdżenie|puste gniazdo
+🪺|gniazdo z jajkami|gniazdowanie|gnieżdżenie
+🫃|brzuch|mężczyzna w ciąży|pełny|w ciąży|wzdęty
+🫄|brzuch|osoba w ciąży|pełny|w ciąży|wzdęty
+🫅|królewski|monarcha|osoba w koronie|szlachta
+🫗|nalewanie płynu|napój|pusty|rozlanie|szklanka
+🫘|fasola|jedzenie|nerkowata|strąk
+🫙|pojemnik|przechowywanie|przyprawa|pusty|słoik|słój|sos|zapas
+🫠|płynna twarz|rozpuszczająca się twarz|roztapiająca się twarz|znikająca twarz
+🫡|ok|pogodna twarz|salutująca twarz|tak jest|wojsko
+🫢|niedowierzanie|niespodzianka|przestrach|przestraszona twarz|twarz z otwartymi oczami i ręką na ustach|zawstydzona twarz|zdumienie
+🫣|gapienie się|podglądanie|twarz z podglądającym okiem|zaabsorbowana twarz
+🫤|do kitu|niepewna twarz|rozczarowana twarz|sceptyczna twarz|twarz z ukośnymi ustami
+🫥|introwertyk|niewidzialna twarz|przygnębiona twarz|twarz otoczona linią przerywaną|ukryta twarz|znikająca twarz
+🫦|dyskomfort|flirt|nerwowość|niepokój|przygryzanie wargi|strach|zmartwienie
+🫧|bąbelki|mycie|mydło|plusk|pod wodą
+🫰|dłoń ze skrzyżowanym palcem wskazującym i kciukiem|drogie|miłość|pieniądze|pstryknięcie palcami|serce
+🫱|dłoń skierowana w prawo|prawa|w prawo
+🫲|dłoń skierowana w lewo|lewa|w lewo
+🫳|dłoń skierowana w dół|odrzucenie|sio|upuszczenie
+🫴|dłoń skierowana w górę|łapanie|propozycja|przywołanie|skinienie
+🫵|palec wskazujący skierowany na patrzącego|ty|wskazywanie
+🫶|dłonie tworzące serce|miłość
+🛝|park rozrywki|wesołe miasteczko|zabawa|zjeżdżalnia
+🛞|koło pojazdu
+🛟|bezpieczeństwo|koło ratunkowe|pływak|ratunek
+🟰|gruby znak równości|matematyka|równość
 😀|roześmiana buźka|szeroko uśmiechnięta twarz
 😃|uśmiechnięta twarz z otwartymi ustami|wesoły
 😄|uśmiechnięta twarz z otwartymi ustami i roześmianymi oczami|wesoły

+ 37 - 0
app/assets/emojis/search-index/pt.csv

@@ -66,6 +66,43 @@
 〰️|ondulado|pontuação|travessão|traço
 ㊗️|parabéns|botão japonês de parabéns|ideograma|japonês|祝|botão "parabéns" em japonês
 ㊙️|segredo|botão japonês de segredo|ideograma|japonês|秘|"segredo"|botão "segredo" em japonês
+🥹|chorar|com raiva|orgulhoso|resistir|rosto segurando as lágrimas|triste|cara a conter as lágrimas
+🧌|conto de fadas|fantasia|monstro|troll
+🩻|doutor|esqueleto|médico|ossos|raio x|raios x
+🩼|ajuda à mobilidade|bastão|bengala|deficiência|machucar|muleta|apoio para mobilidade|canadiana|incapacidade|magoado
+🪩|balada|brilho|dançar|discoteca|festa|globo de espelhos|bola de espelhos
+🪪|cartão de identificação|crachá|credenciais|identidade|identificação|licença|segurança|passe
+🪫|eletrônico|pouca bateria|pouca energia|bateria fraca|eletrónico
+🪬|amuleto|fátima|hamsá|maria|miriam|proteção|chamsá|mão de deus|mão de fátima
+🪷|budismo|flor|hinduísmo|índia|lótus|pureza|vietnã|vietname
+🪸|coral|oceano|recife
+🪹|aninhando|ninho vazio|aninhada|aninhado|aninhar|nidificar
+🪺|aninhando|ninho com ovos|aninhada|aninhado|aninhar|nidificar
+🫃|barriga|cheio|grávido|homem grávido|inchado
+🫄|barriga|cheio|gravidez|inchada|pessoa grávida
+🫅|monarca|nobre|pessoa com coroa|realeza
+🫗|bebida|derramando líquido|derramar|vazio|vidro|a derramar líquido|copo
+🫘|alimento|comida|feijões|legume|feijão
+🫙|armazenar|condimento|jarro|molho|recipiente|vazio|frasco
+🫠|derreter|desaparecer|dissolver|líquido|rosto derretendo|cara a derreter
+🫡|ensolarado|ok|rosto saudando|saudação|sim|tropas|cara a saudar
+🫢|assustado|descrença|embaraçar|espanto|rosto com olhos abertos e mão sobre a boca|surpresa|temor|cara com olhos abertos e mão sobre a boca
+🫣|cativado|espiar|olhar fixamente|rosto com olho espiando|cara com olho a espreitar
+🫤|cético|desapontado|inseguro|meh|rosto com boca diagonal|cara com boca diagonal|duvidoso|incerto
+🫥|deprimido|desaparecer|esconder|introvertido|invisível|rosto com linha pontilhada|cara com linha pontilhada
+🫦|ansioso|desconfortável|flertando|medo|mordendo o lábio|nervoso|preocupado|flartar|flertar|morder o lábio
+🫧|arroto|bolhas|embaixo d’água|limpo|sabão|bolas de sabão|subaquático
+🫰|amor|caro|coração|dinheiro|estalar|mão com dedo indicador e polegar cruzados
+🫱|direita|mão para a direita|para a direita
+🫲|esquerda|mão para a esquerda|para a esquerda
+🫳|cair|descartar|mão com a palma para baixo|xô|deixar cair|largar
+🫴|acenar|mão com a palma para cima|oferecer|pegar|venha
+🫵|apontar|indicador apontando para o visualizador|você|indicador a apontar para o espetador
+🫶|amor|mãos de coração|mãos em forma de coração
+🛝|brincar|escorregador|parque de diversões|parque infantil
+🛞|círculo|girar|pneu|rodar
+🛟|colete salva-vidas|flutuar|resgate|salva-vidas|segurança|boia circular|salvamento
+🟰|igualdade|matemática|sinal de igual pesado
 😀|lol|rindo|risada|rosto risonho|cara sorridente|sorriso
 😃|aberto|boca|rosto risonho com olhos bem abertos|sorriso|aberta|cara sorridente com olhos bem abertos
 😄|aberta|boca|olhos|rosto risonho com olhos sorridentes|sorriso|cara a sorrir com boca aberta e olhos sorridentes

+ 37 - 0
app/assets/emojis/search-index/ru.csv

@@ -66,6 +66,43 @@
 〰️|волна|волнистая|знак|линия|пунктуация
 ㊗️|значок|кнопка|поздравление|праздник|япония
 ㊙️|значок|кнопка|секрет|тайна|япония
+🥹|гордость|грусть|еле сдерживает слезы|злость|сдерживаться|слезы
+🧌|выдуманный персонаж|монстр|сказка|тролль|чудище|чудовище
+🩻|врач|кости|лучи|медицина|просветить|рентген|скелет
+🩼|инвалид|костыли|костыль|отсутствие мобильности|перелом ноги|травма
+🪩|блестеть|вечеринка|диско-шар|дискотека|зеркальный шар|танцы
+🪪|водительские права|документ|паспорт|права|удостоверение личности
+🪫|батарея|мало зарядки|низкий заряд|низкий уровень заряда|разрядилась
+🪬|амулет|защита|ладонь|рука марии|рука мириам|рука фатимы|хамса
+🪷|буддизм|вьетнам|индия|индуизм|лотос|цветок|чистота
+🪸|коралл|океан|риф
+🪹|гнездование|птицы|пустое гнездо
+🪺|гнездо с яйцами|гнездование|птицы|яйца
+🫃|беременный мужчина|в ожидании ребенка|в положении|животик
+🫄|беременный человек|в ожидании ребенка|в положении|животик
+🫅|король|корона|монарх|регалии|царь|человек с короной
+🫗|выливать|наливать|напиток|опустошить|стакан с жидкостью
+🫘|бобовые|еда|кидни|фасоль
+🫙|банка|варенье|мариновать|пустая|соленья|хранить
+🫠|жидкое|исчезает|плавится|растворяется|тает
+🫡|воинское приветствие|войска|да|есть|окей|отдать честь|солнечно
+🫢|изумление|испуг|недоверие|прикрывает рот рукой с открытыми глазами|смущение|страх|удивление|шок
+🫣|взгляд|глаз не отвести|глаз не оторвать|загляденье|подсматривает|подсматривать|пристально смотреть|украдкой
+🫤|досада|недовольство|с перекошенным ртом|скептик|сомнения
+🫥|депрессия|интроверт|исчезать|лицо пунктиром|невидимый|печаль|подавленность|прятаться
+🫦|беспокойство|кусать губы|не в своей тарелке|нервный|переживать|страшно|флирт
+🫧|вода|мыло|мыльные пузыри|пузыри|пузырь|чистый
+🫰|деньги|дорого|жест «деньги»|сердечко|щелчок
+🫱|кисть вправо|рука вправо|рука пальцами вправо
+🫲|кисть влево|рука влево|рука пальцами влево
+🫳|бросить|ладонь вниз|отпустить|рука ладонью вниз
+🫴|ладонь вверх|манить|подзывать|позвать|поймать|рука ладонью вверх
+🫵|ты|указательный палец на себя|указывать
+🫶|жест «сердце»|любовь|руки|сердце
+🛝|горка|дети|детская горка|детская площадка|играть|парк развлечений|скатываться
+🛞|автомобильное колесо|диск|колесо|колпак|круг|поворот|шина
+🛟|безопасность|круг|надувной|помощь|спасательный круг|спасение
+🟰|жирный знак равенства|знак равенства|знак равно|математика|равенство|равно
 😀|лицо|радость|счастье|улыбка|широкая улыбка|широко улыбается
 😃|лицо|радость|рот|смеется|смех|улыбка
 😄|закрытые глаза|радость|смеется с закрытыми глазами|смех|улыбка с закрытыми глазами

+ 37 - 0
app/assets/emojis/search-index/sk.csv

@@ -66,6 +66,43 @@
 〰️|interpunkcia|vlnovka|vlnovková pomlčka|vlnovkovitá
 ㊗️|blahoželám|blahoželanie|ideograf|japončina|japonský ideograf blahoželám
 ㊙️|ideograf|japončina|japonský ideograf tajomstvo|tajomstvo
+🥹|nahnevaná|odolávanie|plač|pyšná|smútok|tvár zadržujúca slzy
+🧌|fantasy|obluda|obor|rozprávka
+🩻|kosti|kostra|lekár|röntgen|zdravie
+🩼|barla|palica|podpera|postihnutie|zdravotná pomôcka|zranenie
+🪩|blyšťanie|disko|párty|tanec|zrkadlová guľa
+🪪|bezpečnosť|doklady|preukaz totožnosti|vodičský preukaz
+🪫|elektronika|málo energie|slabá batéria
+🪬|amulet|fatima|fatimina ruka|hamsa|mária|ochrana|ruka
+🪷|budhizmus|čistota|hinduizmus|india|kvet|lotos|vietnam
+🪸|koral|oceán|útes
+🪹|hniezdenie|prázdne hniezdo
+🪺|hniezdenie|hniezdo s vajcami
+🫃|brucho|nafúknuté|plné|tehotná|tehotný muž
+🫄|brucho|nafúknuté|plné|tehotná osoba|tehotný
+🫅|kráľovský|kráľovstvo|monarcha|osoba s korunou|vládca
+🫗|liatie|nápoj|pohár|prázdny|rozliať
+🫘|fazuľa|jedlo|strukovina
+🫙|nádoba|pohár|prázdny|pyré|zásoby|zaváranina
+🫠|rozpustiť|roztiecť|roztopená tvár|roztopiť|zmiznúť
+🫡|ako povieš|áno|salutovať|salutujúca tvár|slnko v očiach|zdravím
+🫢|nevera|prekvapenie|strach|tvár s otvorenými očami a rukou na ústach|úžas|v rozpakoch|zdesenie
+🫣|pokukávanie|tvár vykúkajúca pomedzi prsty|uchvátená|zízanie
+🫤|ach|neistá|pochybovačná|sklamanie|tvár so šikmými ústami
+🫥|introvert|neviditeľná|skľúčená|skryť|tvár s prerušovanou čiarou|zmiznúť
+🫦|flirt|nepohodlie|nervy|obavy|strach|úzkosť|zahryznutie do pery
+🫧|bubliny|čistota|grgnutie|mydlo|pod vodou
+🫰|drahé|láska|lusknutie|peniaze|ruka s prekríženým ukazovákom a palcom|srdce
+🫱|doprava|ruka|vpravo
+🫲|doľava|ruka|vľavo
+🫳|heš|pustiť|ruka dlaňou nadol|zahodiť
+🫴|chytiť|mávať|poď|ponúkať|ruka dlaňou nahor
+🫵|na teba|ukazovanie ukazovákom
+🫶|láska|srdce z dlaní
+🛝|hra|ihrisko|šmykľavka
+🛞|koleso|kruh|otáčanie|pneumatika
+🛟|bezpečnosť|koleso|pomoc|vznášanie|záchrana|záchranné koleso
+🟰|matematika|rovné|rovnosť
 😀|škeriaca sa tvár|tvár|úškľabok|úškrn
 😃|otvorené|škeriaca sa tvár s veľkými očami|tvár|úsmev|ústa
 😄|oko|prižmúrené|škeriaca sa tvár s prižmúrenými očami|tvár|úsmev|ústa

+ 37 - 0
app/assets/emojis/search-index/tr.csv

@@ -66,6 +66,43 @@
 〰️|çizgi|dalgalı|uzun ses işareti
 ㊗️|japonca "tebrikler" düğmesi|kutlama|sembol|tebrik
 ㊙️|japonca "sır" düğmesi|sembol|sır
+🥹|ağlamaklı|direnmek|gözleri dolmuş yüz|gurur|kızgın|üzgün
+🧌|fantezi|peri masalı|trol|yaratık
+🩻|doktor|iskelet|kemik|röntgen|tıbbi|x ışını
+🩼|baston|değnek|engellilik|koltuk değneği|yürüme yardımı
+🪩|aynalı küre|dans|disko|parlak|parti
+🪪|güvenlik|kimlik bilgisi|kimlik kartı|lisans
+🪫|düşük enerji|düşük pil|elektronik
+🪬|fatıma’nın eli|meryem|muska|nazarlık
+🪷|budizm|çiçek|hindistan|hinduizm|nilüfer|saflık|vietnam
+🪸|mercan|okyanus|resif
+🪹|boş yuva|yuva
+🪺|yumurta bulunan yuva|yuva
+🫃|göbekli|hamile adam|şişmiş
+🫄|göbekli|hamile kişi|şişmiş
+🫅|kraliyet|soylu|taç takmış insan
+🫗|bardak|dökme|dökülen sıvı|içecek|sıvı boş
+🫘|baklagil|fasulye|gıda
+🫙|baharatlık|boş|kavanoz|saklama kabı|sos
+🫠|çözülme|erime|eriyen yüz|kaybolma|sıvılaşma
+🫡|asker|güneşli|onay|selamlayan yüz
+🫢|el ağızdayken gözler açık yüz|hayret|inanamama|korku|şaşkınlık|sürpriz|utanma
+🫣|dikizleme|gizlice bakan yüz|gizlice bakma|gözetleme
+🫤|hayal kırıklığına uğramış|hüsran|kırgın|şüpheli yüz
+🫥|depresif|gizlenme|görünmez|içe dönük|kaybolma|noktalı çizgili yüz
+🫦|davetkar|endişeli|gergin|ısırılan dudak|korku|rahatsız
+🫧|baloncuk|kabarcık|sabun|su altı|temiz
+🫰|aşk|işaret parmağı ve baş parmak birbirine geçmiş el|kalp|pahalı|para
+🫱|el|sağa bakan el
+🫲|el|sola bakan el
+🫳|atmak|avuç içi aşağı bakan el|düşürmek|kışkışlamak
+🫴|avuç içi yukarı bakan el|çağırmak|el etmek|yakalamak
+🫵|işaret|karşıyı işaret eden işaret parmağı|sen
+🫶|aşk|kalp oluşturan eller|sevgi
+🛝|eğlence parkı|kaydırak|oyun
+🛞|çark|dönüş|lastik|tekerlek
+🛟|cankurtaran simidi|güvenlik|hayat kurtaran|yüzme
+🟰|eşitlik|kalın eşittir işareti|matematik
 😀|gülen yüz|gülme|lol|neşeli|sırıtan yüz|yüz
 😃|ağzı açık gülen yüz|ağzı açık sırıtma|gülen yüz|gülme|gülümseme|lol|neşeli|yüz
 😄|ağzı açık gülen gözler|gülen gözlerle ağzı açık sırıtma|gülen yüz|gülme|gülümseme|lol|neşeli

+ 37 - 0
app/assets/emojis/search-index/uk.csv

@@ -66,6 +66,43 @@
 〰️|пунктуація|тире|хвилясте тире|хвилястий
 ㊗️|вітання|ідеограма|ієрогліф|китайський|кнопка "вітання" японською|привітання
 ㊙️|ідеограма|ієрогліф|китайський|кнопка "секрет" японською|секрет
+🥹|гордість|невдоволення|обличчя, що ледве стримує сльози|плач|стримування емоцій|сум|туга
+🧌|казка|монстр|троль|фентезі
+🩻|кістки|лікар|медицина|рентген|скелет
+🩼|втрата дієздатності|засіб відновлення здатності рухатися|інвалідність|костур|милиця|травма|ціпок
+🪩|блискучий|вечірка|дзеркальна куля|дискотека|танці
+🪪|водійське посвідчення|ідентифікаційна картка|особові дані|посвідчення особи|пропуск
+🪫|електроніка|низький заряд батареї
+🪬|амулет|захист|марія|мірʼям|рука фатіми|фатіма|хамса
+🪷|буддизм|вʼєтнам|індія|індуїзм|квітка|лотос|символ чистоти
+🪸|корал|океан|риф
+🪹|гніздування|порожнє гніздо
+🪺|гніздо з пташиними яйцями|гніздування
+🫃|вагітність|живіт|здуття живота|наївся|чоловік із «вагітним» животом
+🫄|вагітна людина|вагітність|великий живіт|здуття живота|ситість
+🫅|аристократія|королівський|людина з короною на голові|монарх|царський
+🫗|виливати|напій|порожня|проливати|склянка, з якої виливається рідина
+🫘|боби|бобові|їжа|квасолина|квасоля
+🫙|банка|ємність|зберігання|порожня|соус|спеції
+🫠|зникати|перехід у рідкий стан|рідина|розплавлене обличчя|розтавати|розчинятися|танути
+🫡|віддавати честь|затулятися від сонця|згода|козиряти|обличчя, що козиряє|ок|салют
+🫢|обличчя з широко відкритими очима, що затискає долонею рот
+🫣|витріщається|обличчя, що підглядає одним оком|підглядає|умирає з цікавості
+🫤|невпевненість|обличчя з ротом навскіс|розчарування|скептицизм|та ну!
+🫥|бажання зникнути|депресія|інтроверт|намагання сховатися|невидимка|обличчя з пунктиром по контуру
+🫦|закушена нижня губа|знервованість|ніяковість|страх|стурбованість|тривога|флірт
+🫧|бульбашки газу|мило|під водою|чистити
+🫰|рука з перехрещеними вказівним і великим пальцями
+🫱|вправо|долоня|праворуч
+🫲|вліво|долоня|ліворуч
+🫳|випустити з руки|відпускаю|киш!|рука долонею вниз
+🫴|ловити|підійди|підкликати|пропозиція|рука долонею вгору
+🫵|ви|вказівний палець, що вказує на глядача|ти|указувати
+🫶|жест «серце»|кохання|любов
+🛝|гратися|дитяча гірка|ігровий майданчик|парк розваг
+🛞|колесо|коло|обертатися|шина
+🛟|безпека|рятівний круг|рятівник|рятувальні засоби|рятувати життя|триматися на воді
+🟰|дорівнює|знак точної рівності|математика|рівність
 😀|вищир|обличчя з широкою усмішкою|широка усмішка|широко всміхнене обличчя
 😃|відкритий|обличчя|посмішка|рот|широко всміхнене обличчя з очищами|широко усміхнене обличчя з великими очима|широко усміхнене обличчя з очищами
 😄|відкритий|обличчя|очі|посмішка|рот|усміхнене обличчя з відкритим ротом і примруженими очима|широко всміхнене обличчя з примруженими очима

BIN
app/assets/emojis/symbols-0.png


BIN
app/assets/emojis/travel-0.png


+ 7 - 7
app/build.gradle

@@ -13,7 +13,7 @@ if (getGradle().getStartParameter().getTaskRequests().toString().contains("Hms")
 }
 
 // version codes
-def app_version = "4.8"
+def app_version = "4.81"
 def beta_suffix = "" // with leading dash
 
 /**
@@ -93,7 +93,7 @@ android {
         vectorDrawables.useSupportLibrary = true
         applicationId "ch.threema.app"
         testApplicationId 'ch.threema.app.test'
-        versionCode 743
+        versionCode 747
         versionName "${app_version}${beta_suffix}"
         resValue "string", "app_name", "Threema"
         // package name used for sync adapter - needs to match mime types below
@@ -688,16 +688,16 @@ dependencies {
     implementation 'androidx.palette:palette-ktx:1.0.0'
     implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
     implementation 'androidx.appcompat:appcompat:1.4.1'
-    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
+    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
     implementation 'androidx.biometric:biometric:1.1.0'
     implementation "androidx.work:work-runtime:2.7.1"
     implementation 'androidx.fragment:fragment-ktx:1.4.1'
     implementation 'androidx.activity:activity-ktx:1.4.0'
     implementation 'androidx.sqlite:sqlite:2.1.0'
     implementation "androidx.concurrent:concurrent-futures:1.1.0"
-    implementation "androidx.camera:camera-camera2:1.1.0-rc01"
-    implementation "androidx.camera:camera-lifecycle:1.1.0-rc01"
-    implementation "androidx.camera:camera-view:1.1.0-rc01"
+    implementation "androidx.camera:camera-camera2:1.1.0-rc02"
+    implementation "androidx.camera:camera-lifecycle:1.1.0-rc02"
+    implementation "androidx.camera:camera-view:1.1.0-rc02"
     implementation 'androidx.multidex:multidex:2.0.1'
     implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
     implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
@@ -712,7 +712,7 @@ dependencies {
     implementation 'androidx.room:room-runtime:2.4.2'
     kapt 'androidx.room:room-compiler:2.4.2'
 
-    implementation 'com.google.android.material:material:1.5.0'
+    implementation 'com.google.android.material:material:1.6.0'
     implementation 'com.google.android.exoplayer:exoplayer-core:2.17.1'
     implementation 'com.google.android.exoplayer:exoplayer-ui:2.17.1'
     implementation 'com.google.zxing:core:3.3.3' // zxing 3.4 crashes on API < 24

+ 8 - 8
app/src/androidTest/java/ch/threema/app/voip/SdpTest.java

@@ -102,23 +102,23 @@ public class SdpTest {
 		}
 
 		@Override
-		public void onIceChecking(long callId) {
-			Log.d(TAG,"onIceChecking");
+		public void onTransportConnecting(long callId) {
+			Log.d(TAG,"onTransportConnecting");
 		}
 
 		@Override
-		public void onIceConnected(long callId) {
-			Log.d(TAG,"onIceConnected");
+		public void onTransportConnected(long callId) {
+			Log.d(TAG,"onTransportConnected");
 		}
 
 		@Override
-		public void onIceDisconnected(long callId) {
-			Log.d(TAG,"onIceDisconnected");
+		public void onTransportDisconnected(long callId) {
+			Log.d(TAG,"onTransportDisconnected");
 		}
 
 		@Override
-		public void onIceFailed(long callId) {
-			Log.d(TAG,"onIceFailed");
+		public void onTransportFailed(long callId) {
+			Log.d(TAG,"onTransportFailed");
 		}
 
 		@Override

+ 2 - 2
app/src/main/java/androidx/core/app/FixedJobIntentService.java

@@ -32,8 +32,8 @@ public abstract class FixedJobIntentService extends JobIntentService {
 		// See https://medium.com/@mohamed.zak/workaround-to-solve-securityexception-caused-by-jobintentservice-1f4b0e688a26
 		try {
 			return super.dequeueWork();
-		} catch (SecurityException exception) {
-			logger.debug("Ignoring SecurityException", exception);
+		} catch (SecurityException|IllegalArgumentException exception) {
+			logger.info("Exception while dequeueing work", exception);
 		}
 		return null;
 	}

+ 1 - 2
app/src/main/java/ch/threema/app/ThreemaApplication.java

@@ -1129,8 +1129,7 @@ public class ThreemaApplication extends MultiDexApplication implements DefaultLi
 				return true;
 			}
 		} catch (IllegalStateException e) {
-			logger.info("Unable to initialize WorkManager");
-			logger.error("Exception", e);
+			logger.error("Unable to initialize WorkManager", e);
 		}
 		return false;
 	}

+ 6 - 33
app/src/main/java/ch/threema/app/activities/ContactDetailActivity.java

@@ -26,7 +26,6 @@ import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.PorterDuff;
 import android.os.AsyncTask;
@@ -56,7 +55,6 @@ import androidx.appcompat.app.ActionBar;
 import androidx.appcompat.view.menu.MenuBuilder;
 import androidx.fragment.app.Fragment;
 import androidx.lifecycle.LifecycleOwner;
-import androidx.palette.graphics.Palette;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 import ch.threema.app.R;
@@ -308,9 +306,6 @@ public class ContactDetailActivity extends ThreemaToolbarActivity
 			return;
 		}
 
-		this.collapsingToolbar = findViewById(R.id.collapsing_toolbar);
-		this.collapsingToolbar.setTitle(" ");
-
 		if (this.contactService == null) {
 			logger.error("no contact service", this);
 			finish();
@@ -324,6 +319,12 @@ public class ContactDetailActivity extends ThreemaToolbarActivity
 			return;
 		}
 
+		this.collapsingToolbar = findViewById(R.id.collapsing_toolbar);
+		this.collapsingToolbar.setTitle(" ");
+		@ColorInt int scrimColor = contactService.getAvatarColor(contact);
+		this.collapsingToolbar.setContentScrimColor(scrimColor);
+		this.collapsingToolbar.setStatusBarScrimColor(scrimColor);
+
 		this.avatarEditView = findViewById(R.id.avatar_edit_view);
 		this.avatarEditView.setHires(true);
 		this.avatarEditView.setContactModel(contact);
@@ -484,36 +485,8 @@ public class ContactDetailActivity extends ThreemaToolbarActivity
 		}
 	}
 
-	private void setScrimColor() {
-		new Thread(new Runnable() {
-			@Override
-			public void run() {
-				@ColorInt int color = getResources().getColor(R.color.material_grey_600);
-				if (contact != null) {
-					final Bitmap bitmap = contactService.getAvatar(contact, false);
-					if (bitmap != null) {
-						Palette palette = Palette.from(bitmap).generate();
-						color = palette.getDarkVibrantColor(getResources().getColor(R.color.material_grey_600));
-					}
-				}
-
-				@ColorInt final int scrimColor = color;
-				RuntimeUtil.runOnUiThread(new Runnable() {
-					@Override
-					public void run() {
-						if (!isFinishing() && !isDestroyed()) {
-							collapsingToolbar.setContentScrimColor(scrimColor);
-							collapsingToolbar.setStatusBarScrimColor(scrimColor);
-						}
-					}
-				});
-			}
-		}).start();
-	}
-
 	private void reload() {
 		this.contactTitle.setText(NameUtil.getDisplayNameOrNickname(contact, true));
-		setScrimColor();
 	}
 
 	@Override

+ 27 - 13
app/src/main/java/ch/threema/app/activities/DirectoryActivity.java

@@ -33,7 +33,6 @@ import android.os.SystemClock;
 import android.text.TextUtils;
 import android.view.MenuItem;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.Toast;
 
 import com.google.android.material.chip.Chip;
@@ -62,7 +61,6 @@ import ch.threema.app.services.ContactService;
 import ch.threema.app.ui.DirectoryDataSourceFactory;
 import ch.threema.app.ui.DirectoryHeaderItemDecoration;
 import ch.threema.app.ui.EmptyRecyclerView;
-import ch.threema.app.ui.EmptyView;
 import ch.threema.app.ui.ThreemaSearchView;
 import ch.threema.app.utils.ConfigUtils;
 import ch.threema.app.utils.IntentDataUtil;
@@ -80,6 +78,7 @@ public class DirectoryActivity extends ThreemaToolbarActivity implements Threema
 	private static final long QUERY_TIMEOUT = 1000; // ms
 	private static final String DIALOG_TAG_CATEGORY_SELECTOR = "cs";
 	public static final String EXTRA_ANIMATE_OUT = "anim";
+	private static final String WILDCARD_SEARCH_ALL = "*";
 
 	private ContactService contactService;
 	private boolean sortByFirstName;
@@ -101,6 +100,10 @@ public class DirectoryActivity extends ThreemaToolbarActivity implements Threema
 	private final Runnable queryTask = new Runnable() {
 		@Override
 		public void run() {
+			if (checkedCategories.size() > 0 && TestUtil.empty(queryText)) {
+				queryText = WILDCARD_SEARCH_ALL;
+			}
+
 			directoryDataSourceFactory.postLiveData.getValue().setQueryText(queryText);
 			directoryDataSourceFactory.postLiveData.getValue().invalidate();
 		}
@@ -183,12 +186,7 @@ public class DirectoryActivity extends ThreemaToolbarActivity implements Threema
 		recyclerView.setHasFixedSize(true);
 		recyclerView.setLayoutManager(new LinearLayoutManager(this));
 		recyclerView.setItemAnimator(new DefaultItemAnimator());
-
-		EmptyView emptyView = new EmptyView(this, getResources().getDimensionPixelSize(R.dimen.directory_search_bar_height)
-															+ ConfigUtils.getActionBarSize(this));
-		emptyView.setup(R.string.directory_empty_view_text);
-		((ViewGroup) recyclerView.getParent().getParent()).addView(emptyView);
-		recyclerView.setEmptyView(emptyView);
+		recyclerView.setEmptyView(findViewById(R.id.empty_text));
 
 		DirectoryHeaderItemDecoration headerItemDecoration = new DirectoryHeaderItemDecoration(getResources().getDimensionPixelSize(R.dimen.directory_header_height), true, getSectionCallback());
 		recyclerView.addItemDecoration(headerItemDecoration);
@@ -359,6 +357,13 @@ public class DirectoryActivity extends ThreemaToolbarActivity implements Threema
 								if (categoryId.equals(checkedCategory.getId())) {
 									checkedCategories.remove(checkedCategory);
 									chipGroup.removeView(v);
+
+									// check if last chip has been removed
+									if (chipGroup.getChildCount() == 0 && WILDCARD_SEARCH_ALL.equals(queryText)) {
+										queryText = "";
+										directoryDataSourceFactory.postLiveData.getValue().setQueryText(queryText);
+									}
+
 									updateDirectory();
 									break;
 								}
@@ -385,17 +390,26 @@ public class DirectoryActivity extends ThreemaToolbarActivity implements Threema
 	public void onYes(String tag, boolean[] checkedItems) {
 		checkedCategories.clear();
 
+		int numCheckedCategories = 0;
 		for(int i = 0; i < checkedItems.length; i++) {
 			if (checkedItems[i]) {
 				checkedCategories.add(categoryList.get(i));
+				numCheckedCategories++;
+			}
+		}
+
+		if (numCheckedCategories > 0) {
+			if (TestUtil.empty(queryText)) {
+				queryText = WILDCARD_SEARCH_ALL;
+			}
+		} else {
+			if (WILDCARD_SEARCH_ALL.equals(queryText)) {
+				queryText = "";
 			}
 		}
+		directoryDataSourceFactory.postLiveData.getValue().setQueryText(queryText);
 
-/* TODO only update if selected items have changed
-		if (!Arrays.equals(this.checkedCategories, checkedItems)) {
-			this.checkedCategories = checkedItems; */
-			updateSelectedCategories();
-//		}
+		updateSelectedCategories();
 	}
 
 	@Override

+ 1 - 2
app/src/main/java/ch/threema/app/activities/DisableBatteryOptimizationsActivity.java

@@ -40,7 +40,6 @@ import org.slf4j.Logger;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
-import androidx.appcompat.app.AppCompatActivity;
 import ch.threema.app.BuildConfig;
 import ch.threema.app.R;
 import ch.threema.app.dialogs.GenericAlertDialog;
@@ -56,7 +55,7 @@ import static ch.threema.app.fragments.BackupDataFragment.REQUEST_ID_DISABLE_BAT
  * If the app has the REQUEST_IGNORE_BATTERY_OPTIMIZATIONS permission, then the
  * ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS intent is used instead.
  */
-public class DisableBatteryOptimizationsActivity extends AppCompatActivity implements GenericAlertDialog.DialogClickListener {
+public class DisableBatteryOptimizationsActivity extends ThreemaActivity implements GenericAlertDialog.DialogClickListener {
 	private static final Logger logger = LoggingUtil.getThreemaLogger("DisableBatteryOptimizationsActivity");
 
 	private static final int REQUEST_CODE_IGNORE_BATTERY_OPTIMIZATIONS = 778;

+ 4 - 43
app/src/main/java/ch/threema/app/activities/GroupDetailActivity.java

@@ -28,7 +28,6 @@ import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Color;
 import android.graphics.PorterDuff;
-import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.text.Html;
@@ -58,7 +57,6 @@ import androidx.core.app.ActivityCompat;
 import androidx.core.app.ActivityOptionsCompat;
 import androidx.lifecycle.Observer;
 import androidx.lifecycle.ViewModelProvider;
-import androidx.palette.graphics.Palette;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 import ch.threema.app.BuildConfig;
@@ -90,7 +88,6 @@ import ch.threema.app.ui.GroupDetailViewModel;
 import ch.threema.app.ui.ResumePauseHandler;
 import ch.threema.app.ui.SelectorDialogItem;
 import ch.threema.app.utils.AppRestrictionUtil;
-import ch.threema.app.utils.BitmapUtil;
 import ch.threema.app.utils.ConfigUtils;
 import ch.threema.app.utils.ContactUtil;
 import ch.threema.app.utils.DialogUtil;
@@ -105,8 +102,6 @@ import ch.threema.localcrypto.MasterKeyLockedException;
 import ch.threema.storage.models.ContactModel;
 import ch.threema.storage.models.GroupModel;
 
-import static ch.threema.app.dialogs.ContactEditDialog.CONTACT_AVATAR_HEIGHT_PX;
-
 public class GroupDetailActivity extends GroupEditActivity implements SelectorDialog.SelectorDialogClickListener,
 	GenericAlertDialog.DialogClickListener,
 	TextEntryDialog.TextEntryDialogClickListener,
@@ -163,8 +158,6 @@ public class GroupDetailActivity extends GroupEditActivity implements SelectorDi
 
 			groupDetailViewModel.setGroupIdentities(groupService.getGroupIdentities(groupModel));
 			sortGroupMembers();
-
-			setScrimColor();
 		}
 	};
 
@@ -173,7 +166,6 @@ public class GroupDetailActivity extends GroupEditActivity implements SelectorDi
 		public void onAvatarSet(File avatarFile1) {
 			groupDetailViewModel.setAvatarFile(avatarFile1);
 			groupDetailViewModel.setIsAvatarRemoved(false);
-			setScrimColor();
 		}
 
 		@Override
@@ -181,7 +173,6 @@ public class GroupDetailActivity extends GroupEditActivity implements SelectorDi
 			groupDetailViewModel.setAvatarFile(null);
 			groupDetailViewModel.setIsAvatarRemoved(true);
 			avatarEditView.setDefaultAvatar(null, groupModel);
-			setScrimColor();
 		}
 	};
 
@@ -406,7 +397,10 @@ public class GroupDetailActivity extends GroupEditActivity implements SelectorDi
 		groupDetailViewModel.getGroupMembers().observe(this, groupMemberObserver);
 		groupDetailViewModel.onDataChanged();
 
-		setScrimColor();
+		@ColorInt int color = groupService.getAvatarColor(groupModel);
+		collapsingToolbar.setContentScrimColor(color);
+		collapsingToolbar.setStatusBarScrimColor(color);
+
 		updateFloatingActionButton();
 
 		if (toolbar.getNavigationIcon() != null) {
@@ -418,39 +412,6 @@ public class GroupDetailActivity extends GroupEditActivity implements SelectorDi
 		ListenerManager.contactListeners.add(this.contactListener);
 	}
 
-	private void setScrimColor() {
-		new Thread(new Runnable() {
-			@Override
-			public void run() {
-				@ColorInt int color = getResources().getColor(R.color.material_grey_600);
-				if (groupModel != null) {
-					Bitmap bitmap;
-
-					if (groupDetailViewModel.getAvatarFile() != null) {
-						bitmap = BitmapUtil.safeGetBitmapFromUri(GroupDetailActivity.this,
-							Uri.fromFile(groupDetailViewModel.getAvatarFile()), CONTACT_AVATAR_HEIGHT_PX);
-					} else {
-						bitmap = groupService.getAvatar(groupModel, false);
-					}
-					if (bitmap != null) {
-						Palette palette = Palette.from(bitmap).generate();
-						color = palette.getDarkVibrantColor(getResources().getColor(R.color.material_grey_600));
-					}
-				}
-				@ColorInt final int scrimColor = color;
-				RuntimeUtil.runOnUiThread(new Runnable() {
-					@Override
-					public void run() {
-						if (!isFinishing() && !isDestroyed()) {
-							collapsingToolbar.setContentScrimColor(scrimColor);
-							collapsingToolbar.setStatusBarScrimColor(scrimColor);
-						}
-					}
-				});
-			}
-		}).start();
-	}
-
 	private void setupAdapter() {
 		this.groupDetailAdapter = new GroupDetailAdapter(this, this.groupModel);
 		this.groupDetailAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {

+ 10 - 6
app/src/main/java/ch/threema/app/activities/HomeActivity.java

@@ -618,7 +618,7 @@ public class HomeActivity extends ThreemaAppCompatActivity implements
 	}
 
 	private void showWhatsNew() {
-		final boolean skipWhatsNew = false; // set this to false if you want to show a What's New screen
+		final boolean skipWhatsNew = true; // set this to false if you want to show a What's New screen
 
 		if (preferenceService != null) {
 			if (!preferenceService.isLatestVersion(this)) {
@@ -1003,11 +1003,15 @@ public class HomeActivity extends ThreemaAppCompatActivity implements
 			}
 
 			// For libre builds of Threema, always ask user to disable battery permissions
-			if (BuildFlavor.forceThreemaPush() && !DisableBatteryOptimizationsActivity.isIgnoringBatteryOptimizations(this)) {
-				final Intent intent = new Intent(this, DisableBatteryOptimizationsActivity.class);
-				intent.putExtra(DisableBatteryOptimizationsActivity.EXTRA_NAME, getString(R.string.threema_push));
-				intent.putExtra(DisableBatteryOptimizationsActivity.EXTRA_CONFIRM, true);
-				startActivityForResult(intent, 12345);
+			if (BuildFlavor.forceThreemaPush()) {
+				preferenceService.setUseThreemaPush(true);
+
+				if (!DisableBatteryOptimizationsActivity.isIgnoringBatteryOptimizations(this)) {
+					final Intent intent = new Intent(this, DisableBatteryOptimizationsActivity.class);
+					intent.putExtra(DisableBatteryOptimizationsActivity.EXTRA_NAME, getString(R.string.threema_push));
+					intent.putExtra(DisableBatteryOptimizationsActivity.EXTRA_CONFIRM, true);
+					startActivityForResult(intent, 12345);
+				}
 			}
 		}
 

+ 3 - 0
app/src/main/java/ch/threema/app/activities/StorageManagementActivity.java

@@ -50,6 +50,7 @@ import java.util.concurrent.TimeUnit;
 import androidx.appcompat.app.ActionBar;
 import androidx.coordinatorlayout.widget.CoordinatorLayout;
 import ch.threema.app.R;
+import ch.threema.app.ThreemaApplication;
 import ch.threema.app.asynctasks.DeleteIdentityAsyncTask;
 import ch.threema.app.dialogs.CancelableHorizontalProgressDialog;
 import ch.threema.app.dialogs.GenericAlertDialog;
@@ -59,6 +60,7 @@ import ch.threema.app.services.ConversationService;
 import ch.threema.app.services.FileService;
 import ch.threema.app.services.MessageService;
 import ch.threema.app.services.UserService;
+import ch.threema.app.utils.ConfigUtils;
 import ch.threema.app.utils.DialogUtil;
 import ch.threema.base.utils.LoggingUtil;
 import ch.threema.storage.models.AbstractMessageModel;
@@ -463,6 +465,7 @@ public class StorageManagementActivity extends ThreemaToolbarActivity implements
 			new DeleteIdentityAsyncTask(getSupportFragmentManager(), new Runnable() {
 				@Override
 				public void run() {
+					ConfigUtils.clearAppData(ThreemaApplication.getAppContext());
 					finishAffinity();
 					System.exit(0);
 				}

+ 1 - 14
app/src/main/java/ch/threema/app/activities/ThreemaActivity.java

@@ -22,7 +22,6 @@
 package ch.threema.app.activities;
 
 import android.content.Intent;
-import android.os.Bundle;
 import android.widget.Toast;
 
 import org.slf4j.Logger;
@@ -75,7 +74,6 @@ public abstract class ThreemaActivity extends ThreemaAppCompatActivity {
 	private boolean isResumed;
 	private String myIdentity = null;
 
-
 	@Override
 	protected void onPause() {
 		super.onPause();
@@ -123,11 +121,6 @@ public abstract class ThreemaActivity extends ThreemaAppCompatActivity {
 		return true;
 	}
 
-	@Override
-	protected void onSaveInstanceState(Bundle outState) {
-		super.onSaveInstanceState(outState);
-	}
-
 	/**
 	 * Return true if master key is unlocked or not protected
 	 *
@@ -153,8 +146,7 @@ public abstract class ThreemaActivity extends ThreemaAppCompatActivity {
 		return true;
 	}
 
-	protected void instantiate() {
-	}
+	protected void instantiate() { }
 
 	protected String getMyIdentity() {
 		if(this.myIdentity == null) {
@@ -166,11 +158,6 @@ public abstract class ThreemaActivity extends ThreemaAppCompatActivity {
 		return this.myIdentity;
 	}
 
-	@Override
-	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-		super.onActivityResult(requestCode, resultCode, data);
-	}
-
 	@Override
 	public void startActivityForResult(Intent intent, int requestCode) {
 		super.startActivityForResult(intent, requestCode);

+ 5 - 0
app/src/main/java/ch/threema/app/activities/wizard/WizardStartActivity.java

@@ -31,6 +31,7 @@ import org.slf4j.Logger;
 
 import androidx.core.app.ActivityCompat;
 import androidx.core.app.ActivityOptionsCompat;
+import androidx.core.app.NotificationManagerCompat;
 import androidx.core.util.Pair;
 import ch.threema.app.R;
 import ch.threema.app.ui.AnimationDrawableCallback;
@@ -38,6 +39,8 @@ import ch.threema.app.utils.ConfigUtils;
 import ch.threema.app.utils.RuntimeUtil;
 import ch.threema.base.utils.LoggingUtil;
 
+import static ch.threema.app.backuprestore.csv.RestoreService.RESTORE_COMPLETION_NOTIFICATION_ID;
+
 public class WizardStartActivity extends WizardBackgroundActivity {
 	private static final Logger logger = LoggingUtil.getThreemaLogger("WizardStartActivity");
 
@@ -48,6 +51,8 @@ public class WizardStartActivity extends WizardBackgroundActivity {
 		super.onCreate(savedInstanceState);
 		setContentView(R.layout.activity_wizard_start);
 
+		NotificationManagerCompat.from(this).cancel(RESTORE_COMPLETION_NOTIFICATION_ID);
+
 		final ImageView imageView = findViewById(R.id.wizard_animation);
 		final AnimationDrawable frameAnimation = (AnimationDrawable) imageView.getBackground();
 		frameAnimation.setOneShot(true);

+ 34 - 11
app/src/main/java/ch/threema/app/adapters/ContactDetailAdapter.java

@@ -22,6 +22,8 @@
 package ch.threema.app.adapters;
 
 import android.Manifest;
+import android.content.ClipData;
+import android.content.ClipboardManager;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.view.LayoutInflater;
@@ -32,14 +34,15 @@ import android.widget.CheckBox;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
+import android.widget.Toast;
 
 import com.google.android.material.textfield.MaterialAutoCompleteTextView;
 
 import org.slf4j.Logger;
-
 import java.util.List;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
 import androidx.appcompat.app.AppCompatActivity;
 import androidx.recyclerview.widget.RecyclerView;
 import ch.threema.app.R;
@@ -113,15 +116,27 @@ public class ContactDetailAdapter extends RecyclerView.Adapter<RecyclerView.View
 			this.readReceiptsSpinner = itemView.findViewById(R.id.read_receipts_spinner);
 			this.typingIndicatorsSpinner = itemView.findViewById(R.id.typing_indicators_spinner);
 
-			verificationLevelIconView.setOnClickListener(new View.OnClickListener() {
-				@Override
-				public void onClick(View v) {
-					if (onClickListener != null) {
-						onClickListener.onVerificationInfoClick(v);
-					}
+			verificationLevelIconView.setOnClickListener(v -> {
+				if (onClickListener != null) {
+					onClickListener.onVerificationInfoClick(v);
 				}
 			});
 
+
+			itemView.findViewById(R.id.threema_id).setOnLongClickListener(ignored -> {
+				String identity = contactModel.getIdentity();
+				copyTextToClipboard(identity, R.string.contact_details_id_copied);
+				return true;
+			});
+
+
+			itemView.findViewById(R.id.public_nickname).setOnLongClickListener(ignored -> {
+				String nickname = contactModel.getPublicNickName();
+				copyTextToClipboard(nickname, R.string.contact_details_nickname_copied);
+				return true;
+			});
+
+
 			itemView.findViewById(R.id.public_key_button).setOnClickListener(v -> {
 				if (context instanceof AppCompatActivity) {
 					PublicKeyDialog.newInstance(context.getString(R.string.public_key_for, contactModel.getIdentity()), contactModel.getPublicKey())
@@ -129,6 +144,13 @@ public class ContactDetailAdapter extends RecyclerView.Adapter<RecyclerView.View
 				}
 			});
 		}
+
+		private void copyTextToClipboard(String data, @StringRes int toastResId) {
+			ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
+			ClipData clip = ClipData.newPlainText(null, data);
+			clipboard.setPrimaryClip(clip);
+			Toast.makeText(context, toastResId, Toast.LENGTH_SHORT).show();
+		}
 	}
 
 	public ContactDetailAdapter(Context context, List<GroupModel> values, ContactModel contactModel) {
@@ -153,12 +175,12 @@ public class ContactDetailAdapter extends RecyclerView.Adapter<RecyclerView.View
 	public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
 		if (viewType == TYPE_ITEM) {
 			View v = LayoutInflater.from(parent.getContext())
-					.inflate(R.layout.item_contact_detail, parent, false);
+				.inflate(R.layout.item_contact_detail, parent, false);
 
 			return new ItemHolder(v);
 		} else if (viewType == TYPE_HEADER) {
 			View v = LayoutInflater.from(parent.getContext())
-					.inflate(R.layout.header_contact_detail, parent, false);
+				.inflate(R.layout.header_contact_detail, parent, false);
 
 			return new HeaderHolder(v);
 		}
@@ -185,7 +207,7 @@ public class ContactDetailAdapter extends RecyclerView.Adapter<RecyclerView.View
 			HeaderHolder headerHolder = (HeaderHolder) holder;
 
 			String identityAdditional = null;
-			if(this.contactModel.getState() != null) {
+			if (this.contactModel.getState() != null) {
 				switch (this.contactModel.getState()) {
 					case ACTIVE:
 						if (blackListIdentityService.has(contactModel.getIdentity())) {
@@ -243,7 +265,7 @@ public class ContactDetailAdapter extends RecyclerView.Adapter<RecyclerView.View
 
 			final String[] choices = context.getResources().getStringArray(R.array.receipts_override_choices);
 			choices[0] = context.getString(R.string.receipts_override_choice_default,
-					choices[preferenceService.isReadReceipts() ? 1 : 2]);
+				choices[preferenceService.isReadReceipts() ? 1 : 2]);
 
 			ArrayAdapter<String> readReceiptsAdapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_dropdown_item, choices);
 			headerHolder.readReceiptsSpinner.setAdapter(readReceiptsAdapter);
@@ -294,6 +316,7 @@ public class ContactDetailAdapter extends RecyclerView.Adapter<RecyclerView.View
 
 	public interface OnClickListener {
 		void onItemClick(View v, GroupModel groupModel);
+
 		void onVerificationInfoClick(View v);
 	}
 

+ 3 - 2
app/src/main/java/ch/threema/app/archive/ArchiveActivity.java

@@ -294,9 +294,10 @@ public class ArchiveActivity extends ThreemaToolbarActivity implements GenericAl
 
 	@SuppressLint("StringFormatInvalid")
 	private void delete(List<ConversationModel> checkedItems) {
-		String confirmText = String.format(getString(R.string.really_delete_thread_message), checkedItems.size());
+		int num = checkedItems.size();
+		String confirmText = getResources().getQuantityString(R.plurals.really_delete_thread_message, num, num) + " " + getString(R.string.messages_cannot_be_recovered);
 
-		if (checkedItems.size() == 1 && checkedItems.get(0).isGroupConversation()) {
+		if (num == 1 && checkedItems.get(0).isGroupConversation()) {
 			if (groupService.isGroupMember(checkedItems.get(0).getGroup())) {
 				if (groupService.isGroupOwner(checkedItems.get(0).getGroup())) {
 					confirmText = getString(R.string.delete_my_group_message);

+ 1 - 4
app/src/main/java/ch/threema/app/asynctasks/DeleteConversationsAsyncTask.java

@@ -39,7 +39,6 @@ import ch.threema.app.dialogs.CancelableHorizontalProgressDialog;
 import ch.threema.app.listeners.ConversationListener;
 import ch.threema.app.managers.ListenerManager;
 import ch.threema.app.managers.ServiceManager;
-import ch.threema.app.services.ContactService;
 import ch.threema.app.services.ConversationService;
 import ch.threema.app.services.DistributionListService;
 import ch.threema.app.services.GroupService;
@@ -55,11 +54,10 @@ public class DeleteConversationsAsyncTask extends AsyncTask<Void, Integer, Integ
 	private final FragmentManager fragmentManager;
 	private final Runnable runOnCompletion;
 	private final List<ConversationModel> conversationModels;
-	private ContactService contactService;
 	private GroupService groupService;
 	private DistributionListService distributionListService;
 	private ConversationService conversationService;
-	private View feedbackView;
+	private final View feedbackView;
 
 	private boolean cancelled = false;
 
@@ -76,7 +74,6 @@ public class DeleteConversationsAsyncTask extends AsyncTask<Void, Integer, Integ
 		ServiceManager serviceManager = ThreemaApplication.getServiceManager();
 
 		try {
-			this.contactService = serviceManager.getContactService();
 			this.groupService = serviceManager.getGroupService();
 			this.distributionListService = serviceManager.getDistributionListService();
 			this.conversationService = serviceManager.getConversationService();

+ 12 - 5
app/src/main/java/ch/threema/app/asynctasks/DeleteIdentityAsyncTask.java

@@ -29,6 +29,7 @@ import org.slf4j.Logger;
 import java.io.File;
 import java.io.IOException;
 
+import androidx.annotation.Nullable;
 import androidx.fragment.app.FragmentManager;
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
@@ -54,8 +55,8 @@ public class DeleteIdentityAsyncTask extends AsyncTask<Void, Void, Exception> {
 	private final FragmentManager fragmentManager;
 	private final Runnable runOnCompletion;
 
-	public DeleteIdentityAsyncTask(FragmentManager fragmentManager,
-	                               Runnable runOnCompletion) {
+	public DeleteIdentityAsyncTask(@Nullable FragmentManager fragmentManager,
+	                               @Nullable Runnable runOnCompletion) {
 
 		this.serviceManager = ThreemaApplication.getServiceManager();
 		this.fragmentManager = fragmentManager;
@@ -64,7 +65,9 @@ public class DeleteIdentityAsyncTask extends AsyncTask<Void, Void, Exception> {
 
 	@Override
 	protected void onPreExecute() {
-		GenericProgressDialog.newInstance(R.string.delete_id_title, R.string.please_wait).show(fragmentManager, DIALOG_TAG_DELETING_ID);
+		if (fragmentManager != null) {
+			GenericProgressDialog.newInstance(R.string.delete_id_title, R.string.please_wait).show(fragmentManager, DIALOG_TAG_DELETING_ID);
+		}
 	}
 
 	@Override
@@ -78,7 +81,9 @@ public class DeleteIdentityAsyncTask extends AsyncTask<Void, Void, Exception> {
 			serviceManager.getConversationService().reset();
 			serviceManager.getGroupService().removeAll();
 			serviceManager.getContactService().removeAll();
-			serviceManager.getUserService().removeIdentity();
+			try {
+				serviceManager.getUserService().removeIdentity();
+			} catch (Exception ignored) {}
 			serviceManager.getDistributionListService().removeAll();
 			serviceManager.getBallotService().removeAll();
 			serviceManager.getPreferenceService().clear();
@@ -140,7 +145,9 @@ public class DeleteIdentityAsyncTask extends AsyncTask<Void, Void, Exception> {
 
 	@Override
 	protected void onPostExecute(Exception exception) {
-		DialogUtil.dismissDialog(fragmentManager, DIALOG_TAG_DELETING_ID, true);
+		if (fragmentManager != null) {
+			DialogUtil.dismissDialog(fragmentManager, DIALOG_TAG_DELETING_ID, true);
+		}
 		if (exception != null) {
 			Toast.makeText(ThreemaApplication.getAppContext(), exception.getMessage(), Toast.LENGTH_LONG).show();
 		} else {

+ 30 - 20
app/src/main/java/ch/threema/app/backuprestore/csv/RestoreService.java

@@ -63,6 +63,7 @@ import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
 import ch.threema.app.activities.DummyActivity;
 import ch.threema.app.activities.HomeActivity;
+import ch.threema.app.asynctasks.DeleteIdentityAsyncTask;
 import ch.threema.app.backuprestore.BackupRestoreDataService;
 import ch.threema.app.collections.Functional;
 import ch.threema.app.collections.IPredicateNonNull;
@@ -1625,37 +1626,46 @@ public class RestoreService extends Service {
 			preferenceService.setWizardRunning(true);
 
 			showRestoreSuccessNotification();
-		} else {
+
+			//try to reopen connection
 			try {
-				this.userService.removeIdentity();
+				if (!serviceManager.getConnection().isRunning()) {
+					serviceManager.startConnection();
+				}
 			} catch (Exception e) {
 				logger.error("Exception", e);
 			}
-			showRestoreErrorNotification(message);
-		}
 
-		//try to reopen connection
-		try {
-			if (!serviceManager.getConnection().isRunning()) {
-				serviceManager.startConnection();
+			if (wakeLock != null && wakeLock.isHeld()) {
+				logger.debug("releasing wakelock");
+				wakeLock.release();
 			}
-		} catch (Exception e) {
-			logger.error("Exception", e);
-		}
 
-		if (wakeLock != null && wakeLock.isHeld()) {
-			logger.debug("releasing wakelock");
-			wakeLock.release();
-		}
+			stopForeground(true);
 
-		stopForeground(true);
+			isRunning = false;
 
-		isRunning = false;
+			if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
+				ConfigUtils.scheduleAppRestart(getApplicationContext(), 2 * (int) DateUtils.SECOND_IN_MILLIS, getApplicationContext().getResources().getString(R.string.ipv6_restart_now));
+			}
+			stopSelf();
+		} else {
+			try {
+				this.userService.removeIdentity();
+			} catch (Exception e) {
+				logger.error("Exception", e);
+			}
+			showRestoreErrorNotification(message);
 
-		if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
-			ConfigUtils.scheduleAppRestart(getApplicationContext(), 2 * (int) DateUtils.SECOND_IN_MILLIS, getApplicationContext().getResources().getString(R.string.ipv6_restart_now));
+			new DeleteIdentityAsyncTask(null, new Runnable() {
+				@Override
+				public void run() {
+					isRunning = false;
+
+					System.exit(0);
+				}
+			}).execute();
 		}
-		stopSelf();
 	}
 
 	private Notification getPersistentNotification() {

+ 20 - 12
app/src/main/java/ch/threema/app/camera/CameraFragment.kt

@@ -81,7 +81,7 @@ class CameraFragment : Fragment() {
     private val RECORDING_MODE_IMAGE = 0
     private val RECORDING_MODE_VIDEO = 1
 
-    private val viewModel: CameraFragmentViewModel by viewModels()
+    private lateinit var viewModel: CameraFragmentViewModel
 
     private var displayId: Int = -1
     private var preview: Preview? = null
@@ -261,16 +261,14 @@ class CameraFragment : Fragment() {
 
         // Wait for the views to be properly laid out
         previewView!!.post {
-            if (previewView != null) {
-                // Keep track of the display in which this view is attached
-                displayId = previewView!!.display.displayId
+            // Keep track of the display in which this view is attached
+            displayId = previewView?.display?.displayId ?: -1
 
-                // Build UI controls
-                updateCameraUi()
+            // Build UI controls
+            updateCameraUi()
 
-                // Set up the camera and its use cases
-                setUpCamera()
-            }
+            // Set up the camera and its use cases
+            setUpCamera()
         }
     }
 
@@ -286,6 +284,9 @@ class CameraFragment : Fragment() {
     override fun onAttach(context: Context) {
         super.onAttach(context)
 
+        val viewModel: CameraFragmentViewModel by viewModels()
+        this.viewModel = viewModel
+
         check(activity is CameraCallback) { "Activity does not implement CameraCallback." }
         cameraCallback = activity as CameraCallback?
         cameraConfiguration = activity as CameraConfiguration?
@@ -325,7 +326,6 @@ class CameraFragment : Fragment() {
     private fun setUpCamera() {
         val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
         cameraProviderFuture.addListener({
-
             // CameraProvider
             cameraProvider = cameraProviderFuture.get()
 
@@ -769,12 +769,20 @@ class CameraFragment : Fragment() {
 
     /** Returns true if the device has an available back camera. False otherwise */
     private fun hasBackCamera(): Boolean {
-        return cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false
+        return try {
+            cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false
+        } catch (e: Exception) {
+            false
+        }
     }
 
     /** Returns true if the device has an available front camera. False otherwise */
     private fun hasFrontCamera(): Boolean {
-        return cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?: false
+        return try {
+            return cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?: false
+        } catch (e: Exception) {
+            false
+        }
     }
 
     private fun switchFlash() {

+ 12 - 8
app/src/main/java/ch/threema/app/dialogs/MessageDetailDialog.java

@@ -94,6 +94,8 @@ public class MessageDetailDialog extends ThreemaDialogFragment {
 			final TextView deliveredDate = dialogView.findViewById(R.id.delivered_date);
 			final TextView readText = dialogView.findViewById(R.id.read_text);
 			final TextView readDate = dialogView.findViewById(R.id.read_date);
+			final TextView modifiedText = dialogView.findViewById(R.id.modified_text);
+			final TextView modifiedDate = dialogView.findViewById(R.id.modified_date);
 			final TextView messageIdText = dialogView.findViewById(R.id.messageid_text);
 			final TextView messageIdDate = dialogView.findViewById(R.id.messageid_date);
 			final TextView mimeTypeText = dialogView.findViewById(R.id.filetype_text);
@@ -142,23 +144,25 @@ public class MessageDetailDialog extends ThreemaDialogFragment {
 						Date readAt = messageModel.getReadAt();
 						Date modifiedAt = messageModel.getModifiedAt();
 
-						if (readAt != null && deliveredAt != null) {
+						if (deliveredAt != null) {
 							deliveredText.setText(TextUtil.capitalize(getString(R.string.state_delivered)));
 							deliveredDate.setText(LocaleUtil.formatTimeStampStringAbsolute(getContext(), deliveredAt.getTime()));
 							deliveredText.setVisibility(View.VISIBLE);
 							deliveredDate.setVisibility(View.VISIBLE);
+						}
 
+						if (readAt != null) {
 							readText.setText(TextUtil.capitalize(getString(R.string.state_read)));
 							readDate.setText(LocaleUtil.formatTimeStampStringAbsolute(getContext(), readAt.getTime()));
 							readText.setVisibility(View.VISIBLE);
 							readDate.setVisibility(View.VISIBLE);
-						} else {
-							deliveredText.setText(TextUtil.capitalize(getString(stateResource)));
-							deliveredDate.setText(modifiedAt != null ?
-								LocaleUtil.formatTimeStampStringAbsolute(getContext(), messageModel.getModifiedAt().getTime()) :
-								(messageModel.getPostedAt(true) != null ? LocaleUtil.formatTimeStampStringAbsolute(getContext(), messageModel.getPostedAt(true).getTime()) : ""));
-							deliveredText.setVisibility(View.VISIBLE);
-							deliveredDate.setVisibility(View.VISIBLE);
+						}
+
+						if (modifiedAt != null && !(messageState == MessageState.READ && modifiedAt.equals(readAt))) {
+							modifiedText.setText(TextUtil.capitalize(getString(stateResource)));
+							modifiedDate.setText(LocaleUtil.formatTimeStampStringAbsolute(getContext(), modifiedAt.getTime()));
+							modifiedText.setVisibility(View.VISIBLE);
+							modifiedDate.setVisibility(View.VISIBLE);
 						}
 					}
 				} else {

+ 1 - 1
app/src/main/java/ch/threema/app/dialogs/RateDialog.java

@@ -61,7 +61,7 @@ public class RateDialog extends ThreemaDialogFragment {
 	private String tag = null;
 	private PreferenceService preferenceService;
 
-	private Integer starMap[] = {
+	private final Integer[] starMap = {
 			R.id.star_one,
 			R.id.star_two,
 			R.id.star_three,

+ 45 - 10
app/src/main/java/ch/threema/app/dialogs/TextWithCheckboxDialog.java

@@ -40,17 +40,37 @@ public class TextWithCheckboxDialog extends ThreemaDialogFragment {
 	private TextWithCheckboxDialogClickListener callback;
 	private Activity activity;
 
+	private final static String TITLE_KEY = "title";
+	private final static String MESSAGE_KEY = "message";
+	private final static String MESSAGE_RES_KEY = "messageRes";
+	private final static String CHECKBOX_LABEL_KEY = "checkboxLabel";
+	private final static String POSITIVE_KEY = "positive";
+	private final static String NEGATIVE_KEY = "negative";
+
 	public interface TextWithCheckboxDialogClickListener {
 		void onYes(String tag, Object data, boolean checked);
 	}
 
+	public static TextWithCheckboxDialog newInstance(String title, @StringRes int messageRes, @StringRes int checkboxLabel, @StringRes int positive, @StringRes int negative) {
+		TextWithCheckboxDialog dialog = new TextWithCheckboxDialog();
+		Bundle args = new Bundle();
+		args.putString(TITLE_KEY, title);
+		args.putInt(MESSAGE_RES_KEY, messageRes);
+		args.putInt(CHECKBOX_LABEL_KEY, checkboxLabel);
+		args.putInt(POSITIVE_KEY, positive);
+		args.putInt(NEGATIVE_KEY, negative);
+
+		dialog.setArguments(args);
+		return dialog;
+	}
+
 	public static TextWithCheckboxDialog newInstance(String message, @StringRes int checkboxLabel, @StringRes int positive, @StringRes int negative) {
 		TextWithCheckboxDialog dialog = new TextWithCheckboxDialog();
 		Bundle args = new Bundle();
-		args.putString("message", message);
-		args.putInt("checkboxLabel", checkboxLabel);
-		args.putInt("positive", positive);
-		args.putInt("negative", negative);
+		args.putString(MESSAGE_KEY, message);
+		args.putInt(CHECKBOX_LABEL_KEY, checkboxLabel);
+		args.putInt(POSITIVE_KEY, positive);
+		args.putInt(NEGATIVE_KEY, negative);
 
 		dialog.setArguments(args);
 		return dialog;
@@ -83,26 +103,41 @@ public class TextWithCheckboxDialog extends ThreemaDialogFragment {
 		}
 	}
 
+	/**
+	 * Set the callback of this dialog.
+	 *
+	 * @param clickListener the listener
+	 */
+	public void setCallback(TextWithCheckboxDialogClickListener clickListener) {
+		callback = clickListener;
+	}
+
 	@NonNull
 	@Override
 	public AppCompatDialog onCreateDialog(Bundle savedInstanceState) {
-		String message = getArguments().getString("message");
-		@StringRes int checkboxLabel = getArguments().getInt("checkboxLabel");
-		@StringRes int positive = getArguments().getInt("positive");
-		@StringRes int negative = getArguments().getInt("negative");
+		String title = getArguments().getString(TITLE_KEY);
+		String message = getArguments().getString(MESSAGE_KEY);
+		int messageRes = getArguments().getInt(MESSAGE_RES_KEY, 0);
+		@StringRes int checkboxLabel = getArguments().getInt(CHECKBOX_LABEL_KEY);
+		@StringRes int positive = getArguments().getInt(POSITIVE_KEY);
+		@StringRes int negative = getArguments().getInt(NEGATIVE_KEY);
 
 		final View dialogView = activity.getLayoutInflater().inflate(R.layout.dialog_text_with_checkbox, null);
 		final AppCompatCheckBox checkbox = dialogView.findViewById(R.id.checkbox);
-
 		final String tag = this.getTag();
 
 		MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity(), getTheme())
-			.setTitle(message)
+			.setTitle(title != null ? title : message)
 			.setView(dialogView)
 			.setCancelable(false)
 			.setNegativeButton(negative, null)
 			.setPositiveButton(positive, (dialog, which) -> callback.onYes(tag, object, checkbox.isChecked()));
 
+		if (messageRes != 0) {
+			checkbox.setTextSize(14);
+			builder.setMessage(messageRes);
+		}
+
 		checkbox.setChecked(false);
 		if (checkboxLabel != 0) {
 			checkbox.setText(checkboxLabel);

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 4609 - 4346
app/src/main/java/ch/threema/app/emojis/EmojiParser.java


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 404 - 332
app/src/main/java/ch/threema/app/emojis/EmojiSpritemap.java


+ 2 - 2
app/src/main/java/ch/threema/app/emojis/search/EmojiSearchIndex.kt

@@ -31,7 +31,7 @@ import java.io.File
 import java.io.IOException
 import java.io.InputStreamReader
 
-val logger = LoggingUtil.getThreemaLogger("EmojiSearchIndex")
+private val logger = LoggingUtil.getThreemaLogger("EmojiSearchIndex")
 
 class EmojiSearchIndex(
 	private val context: Context,
@@ -43,7 +43,7 @@ class EmojiSearchIndex(
 	private var searchIndexVersion = preferenceService.emojiSearchIndexVersion
 
 	private companion object {
-		const val SEARCH_INDEX_VERSION = 3
+		const val SEARCH_INDEX_VERSION = 5
 		const val INDEX_FILE_EXTENSION = ".csv"
 		const val EMOJI_ORDERS_FILE = "orders.csv"
 		const val EMOJI_DIVERSITIES_FILE = "diversities.csv"

+ 156 - 6
app/src/main/java/ch/threema/app/fragments/ComposeMessageFragment.java

@@ -176,6 +176,7 @@ import ch.threema.app.services.WallpaperService;
 import ch.threema.app.services.ballot.BallotService;
 import ch.threema.app.services.license.LicenseService;
 import ch.threema.app.services.messageplayer.MessagePlayerService;
+import ch.threema.app.stores.IdentityStore;
 import ch.threema.app.ui.AvatarView;
 import ch.threema.app.ui.ContentCommitComposeEditText;
 import ch.threema.app.ui.ConversationListView;
@@ -185,6 +186,7 @@ import ch.threema.app.ui.LockableScrollView;
 import ch.threema.app.ui.MentionSelectorPopup;
 import ch.threema.app.ui.OpenBallotNoticeView;
 import ch.threema.app.ui.QRCodePopup;
+import ch.threema.app.ui.ReportSpamView;
 import ch.threema.app.ui.SelectorDialogItem;
 import ch.threema.app.ui.SendButton;
 import ch.threema.app.ui.SingleToast;
@@ -222,6 +224,8 @@ import ch.threema.app.voip.services.VoipStateService;
 import ch.threema.app.voip.util.VoipUtil;
 import ch.threema.base.utils.LoggingUtil;
 import ch.threema.domain.models.IdentityType;
+import ch.threema.domain.models.VerificationLevel;
+import ch.threema.domain.protocol.api.APIConnector;
 import ch.threema.domain.protocol.csp.messages.file.FileData;
 import ch.threema.storage.DatabaseServiceNew;
 import ch.threema.storage.models.AbstractMessageModel;
@@ -255,6 +259,7 @@ public class ComposeMessageFragment extends Fragment implements
 	EmojiPicker.EmojiPickerListener,
 	MentionSelectorPopup.MentionSelectorListener,
 	OpenBallotNoticeView.VisibilityListener,
+	ReportSpamView.OnReportButtonClickListener,
 	ThreemaToolbarActivity.OnSoftKeyboardChangedListener,
 	ExpandableTextEntryDialog.ExpandableTextEntryDialogClickListener {
 
@@ -363,6 +368,7 @@ public class ComposeMessageFragment extends Fragment implements
 	private TooltipPopup workTooltipPopup;
 	private OpenBallotNoticeView openBallotNoticeView;
 	private OpenGroupRequestNoticeView openGroupRequestNoticeView;
+	private ReportSpamView reportSpamView;
 	private ComposeMessageActivity activity;
 	private View fragmentView;
 	private CoordinatorLayout coordinatorLayout;
@@ -509,6 +515,10 @@ public class ComposeMessageFragment extends Fragment implements
 							if (addMessageToList(newMessage)) {
 								if (!newMessage.isStatusMessage() && (newMessage.getType() != MessageType.VOIP_STATUS)) {
 									playSentSound();
+
+									if (reportSpamView != null && reportSpamView.getVisibility() == View.VISIBLE) {
+										reportSpamView.hide();
+									}
 								}
 							}
 						} else {
@@ -949,6 +959,8 @@ public class ComposeMessageFragment extends Fragment implements
 			this.bottomPanel = this.fragmentView.findViewById(R.id.bottom_panel);
 			this.openBallotNoticeView = this.fragmentView.findViewById(R.id.open_ballots_layout);
 			this.openGroupRequestNoticeView = this.fragmentView.findViewById(R.id.open_group_requests_layout);
+			this.reportSpamView = this.fragmentView.findViewById(R.id.report_spam_layout);
+			this.reportSpamView.setListener(this);
 
 			this.getValuesFromBundle(savedInstanceState);
 			this.handleIntent(activity.getIntent());
@@ -1740,6 +1752,7 @@ public class ComposeMessageFragment extends Fragment implements
 		this.isGroupChat = false;
 		this.isDistributionListChat = false;
 		this.currentPageReferenceId = null;
+		this.reportSpamView.hide();
 
 		//remove old indicator every time!
 		//fix ANDR-432
@@ -1850,7 +1863,7 @@ public class ComposeMessageFragment extends Fragment implements
 			}
 		}
 
-		this.initConversationList(intent.hasExtra(EXTRA_API_MESSAGE_ID) && intent.hasExtra(EXTRA_SEARCH_QUERY) ? (Runnable) () -> {
+		this.initConversationList(intent.hasExtra(EXTRA_API_MESSAGE_ID) && intent.hasExtra(EXTRA_SEARCH_QUERY) ? () -> {
 				String apiMessageId = intent.getStringExtra(EXTRA_API_MESSAGE_ID);
 				String searchQuery = intent.getStringExtra(EXTRA_SEARCH_QUERY);
 
@@ -1884,7 +1897,11 @@ public class ComposeMessageFragment extends Fragment implements
 				} else {
 					Toast.makeText(ThreemaApplication.getAppContext(), R.string.message_not_found, Toast.LENGTH_SHORT).show();
 				}
-		} : null);
+		} : () -> {
+			if (isPossibleSpamContact(contactModel)) {
+				reportSpamView.show(contactModel);
+			}
+		});
 
 		// work around the problem that the same original intent may be sent
 		// each time a singleTop activity (like this one) is coming back to front
@@ -2010,6 +2027,85 @@ public class ComposeMessageFragment extends Fragment implements
 		}
 	}
 
+	/**
+	 * Check if the clues indicate that the sender of this chat might be a spammer
+	 * @param contactModel Contact model of possible spammer
+	 * @return true if the contact could be a spammer, false otherwise
+	 */
+	private boolean isPossibleSpamContact(ContactModel contactModel) {
+		if (ConfigUtils.isOnPremBuild()) {
+			return false;
+		}
+
+		if (contactModel == null || contactModel.getVerificationLevel() != VerificationLevel.UNVERIFIED) {
+			return false;
+		}
+
+		if (!TestUtil.empty(contactModel.getFirstName()) || !TestUtil.empty(contactModel.getLastName())) {
+			return false;
+		}
+
+		if (blackListIdentityService.has(contactModel.getIdentity())) {
+			return false;
+		}
+
+		if (contactModel.isHidden()) {
+			return false;
+		}
+
+		if (composeMessageAdapter == null) {
+			return false;
+		}
+
+		int numMessages = composeMessageAdapter.getCount();
+
+		if (numMessages >= MESSAGE_PAGE_SIZE || numMessages < 2) {
+			return false;
+		}
+
+		AbstractMessageModel firstMessageModel;
+		int positionOfFirstIncomingMessage = 0;
+		for (int i = 0; i < numMessages; i++) {
+			firstMessageModel = composeMessageAdapter.getItem(i);
+			if (firstMessageModel == null) {
+				return false;
+			}
+			if (firstMessageModel.isOutbox()) {
+				return false;
+			}
+			if (contactModel.getIdentity().equals(firstMessageModel.getIdentity())) {
+				positionOfFirstIncomingMessage = i;
+				break;
+			}
+		}
+
+		AbstractMessageModel messageModel = composeMessageAdapter.getItem(positionOfFirstIncomingMessage);
+
+		if (messageModel == null) {
+			return false;
+		}
+
+		Date contactCreated = contactModel.getDateCreated();
+		Date firstMessageDate = messageModel.getCreatedAt();
+
+		if (contactCreated == null || firstMessageDate == null) {
+			return false;
+		}
+
+		if (firstMessageDate.getTime() - contactCreated.getTime() > DateUtils.DAY_IN_MILLIS) {
+			return false;
+		}
+
+		for (int i = positionOfFirstIncomingMessage; i < numMessages; i++) {
+			AbstractMessageModel abstractMessageModel = composeMessageAdapter.getItem(i);
+			if (abstractMessageModel == null || abstractMessageModel.isOutbox()) {
+				return false;
+			}
+		}
+
+		return true;
+	}
+
 	private void undoDeleteMessages() {
 		synchronized (deleteableMessages) {
 			for (Pair<AbstractMessageModel, Integer> m : deleteableMessages) {
@@ -2538,10 +2634,21 @@ public class ComposeMessageFragment extends Fragment implements
 			Map<String, Integer> colors = new HashMap<>();
 			boolean darkTheme = ConfigUtils.getAppTheme(activity) == ConfigUtils.THEME_DARK;
 			for (Map.Entry<String, Integer> entry : colorIndices.entrySet()) {
-				colors.put(entry.getKey(),
+				String memberIdentity = entry.getKey();
+				int memberColorIndex = entry.getValue();
+				// If the ID color index is -1, the correct index is calculated and stored into the database
+				if (memberColorIndex < 0) {
+					ContactModel member = contactService.getByIdentity(memberIdentity);
+					if (member != null) {
+						member.initializeIdColor();
+						memberColorIndex = member.getIdColorIndex();
+						contactService.save(member);
+					}
+				}
+				colors.put(memberIdentity,
 					darkTheme ?
-						ColorUtil.getInstance().getIDColorDark(entry.getValue()) :
-						ColorUtil.getInstance().getIDColorLight(entry.getValue()));
+						ColorUtil.getInstance().getIDColorDark(memberColorIndex) :
+						ColorUtil.getInstance().getIDColorLight(memberColorIndex));
 			}
 
 			this.identityColors = colors;
@@ -3477,7 +3584,7 @@ public class ComposeMessageFragment extends Fragment implements
 	}
 
 	private void emptyChat() {
-		new EmptyChatAsyncTask(messageReceiver, messageService, conversationService, getFragmentManager(), false, () -> {
+		new EmptyChatAsyncTask(messageReceiver, messageService, conversationService, getParentFragmentManager(), false, () -> {
 			if (isAdded()) {
 					synchronized (messageValues) {
 						messageValues.clear();
@@ -3835,6 +3942,12 @@ public class ComposeMessageFragment extends Fragment implements
 			convListView.clearChoices();
 			convListView.requestLayout();
 			convListView.post(() -> convListView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE));
+
+			// If the action mode has been left without clearing up the selected messages, we need
+			// to trigger a refresh so that linkified links work again (selectedMessages will be cleared lazily)
+			if (!selectedMessages.isEmpty() && composeMessageAdapter != null) {
+				composeMessageAdapter.notifyDataSetChanged();
+			}
 		}
 	}
 
@@ -4331,6 +4444,43 @@ public class ComposeMessageFragment extends Fragment implements
 		}
 	}
 
+	@Override
+	public void onReportSpamClicked(@NonNull final ContactModel spammerContactModel, boolean block) {
+		final APIConnector connector = ThreemaApplication.requireServiceManager().getAPIConnector();
+		final IdentityStore identityStore = ThreemaApplication.requireServiceManager().getIdentityStore();
+
+		new Thread(() -> {
+			try {
+				connector.reportJunk(identityStore, spammerContactModel.getIdentity(), spammerContactModel.getPublicNickName());
+				if (block) {
+					blackListIdentityService.add(spammerContactModel.getIdentity());
+				}
+				contactModel.setIsHidden(true);
+				contactService.save(contactModel);
+
+				RuntimeUtil.runOnUiThread(() -> {
+					Toast.makeText(ComposeMessageFragment.this.getContext(), R.string.spam_successfully_reported, Toast.LENGTH_LONG).show();
+					if (block) {
+						new EmptyChatAsyncTask(messageReceiver, messageService, conversationService, getParentFragmentManager(), true, new Runnable() {
+							@Override
+							public void run() {
+								ListenerManager.conversationListeners.handle(ConversationListener::onModifiedAll);
+								ListenerManager.contactListeners.handle(listener -> listener.onModified(contactModel));
+								ComposeMessageFragment.this.finishActivity();
+							}
+						}).execute();
+					} else {
+						reportSpamView.hide();
+						ListenerManager.contactListeners.handle(listener -> listener.onModified(contactModel));
+					}
+				});
+			} catch (Exception e) {
+				logger.error("Error reporting spam", e);
+				RuntimeUtil.runOnUiThread(() -> Toast.makeText(ComposeMessageFragment.this.getContext(), getString(R.string.spam_error_reporting, e.getMessage()), Toast.LENGTH_LONG).show());
+			}
+		}).start();
+	}
+
 	private void finishActivity() {
 		if (activity != null) {
 			activity.finish();

+ 1 - 1
app/src/main/java/ch/threema/app/fragments/ContactsSectionFragment.java

@@ -697,7 +697,7 @@ public class ContactsSectionFragment
 				}
 				if (numContacts > 1) {
 					final StringBuilder builder = new StringBuilder();
-					builder.append(numContacts).append(" ").append(getString(R.string.title_section2));
+					builder.append(getResources().getQuantityString(R.plurals.contacts_counter_label, numContacts, numContacts));
 					if (counts != null) {
 						builder.append(" (+").append(counts.last30d).append(" / ").append(getString(R.string.thirty_days_abbrev)).append(")");
 					}

+ 24 - 12
app/src/main/java/ch/threema/app/fragments/MyIDFragment.java

@@ -192,6 +192,13 @@ public class MyIDFragment extends MainFragment
 		setRetainInstance(true);
 	}
 
+	@Override
+	public void onResume() {
+		super.onResume();
+
+		setupPicReleaseSpinner();
+	}
+
 	@Nullable
 	@Override
 	public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
@@ -271,18 +278,7 @@ public class MyIDFragment extends MainFragment
 				this.fragmentView.findViewById(R.id.profile_edit).setOnClickListener(this);
 			}
 
-			MaterialAutoCompleteTextView spinner = fragmentView.findViewById(R.id.picrelease_spinner);
-			ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(getContext(), R.array.picrelease_choices, android.R.layout.simple_spinner_dropdown_item);
-			spinner.setAdapter(adapter);
-			spinner.setText(adapter.getItem(preferenceService.getProfilePicRelease()), false);
-			spinner.setOnItemClickListener((parent, view, position, id) -> {
-				int oldPosition = preferenceService.getProfilePicRelease();
-				preferenceService.setProfilePicRelease(position);
-				picReleaseConfImageView.setVisibility(position == PreferenceService.PROFILEPIC_RELEASE_SOME ? View.VISIBLE : View.GONE);
-				if (position == PreferenceService.PROFILEPIC_RELEASE_SOME && position != oldPosition) {
-					launchProfilePictureRecipientsSelector(view);
-				}
-			});
+			setupPicReleaseSpinner();
 
 			if (isDisabledProfilePicReleaseSettings) {
 				fragmentView.findViewById(R.id.picrelease_spinner).setVisibility(View.GONE);
@@ -298,6 +294,21 @@ public class MyIDFragment extends MainFragment
 		return fragmentView;
 	}
 
+	private void setupPicReleaseSpinner() {
+		MaterialAutoCompleteTextView spinner = fragmentView.findViewById(R.id.picrelease_spinner);
+		ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(getContext(), R.array.picrelease_choices, android.R.layout.simple_spinner_dropdown_item);
+		spinner.setAdapter(adapter);
+		spinner.setText(adapter.getItem(preferenceService.getProfilePicRelease()), false);
+		spinner.setOnItemClickListener((parent, view, position, id) -> {
+			int oldPosition = preferenceService.getProfilePicRelease();
+			preferenceService.setProfilePicRelease(position);
+			fragmentView.findViewById(R.id.picrelease_config).setVisibility(position == PreferenceService.PROFILEPIC_RELEASE_SOME ? View.VISIBLE : View.GONE);
+			if (position == PreferenceService.PROFILEPIC_RELEASE_SOME && position != oldPosition) {
+				launchProfilePictureRecipientsSelector(view);
+			}
+		});
+	}
+
 	@Override
 	public void onStart() {
 		super.onStart();
@@ -500,6 +511,7 @@ public class MyIDFragment extends MainFragment
 		new DeleteIdentityAsyncTask(getFragmentManager(), new Runnable() {
 			@Override
 			public void run() {
+				ConfigUtils.clearAppData(ThreemaApplication.getAppContext());
 				System.exit(0);
 			}
 		}).execute();

+ 15 - 2
app/src/main/java/ch/threema/app/jobs/ReConnectJobService.java

@@ -32,11 +32,16 @@ import ch.threema.base.utils.LoggingUtil;
 
 public class ReConnectJobService extends JobService {
 	private static final Logger logger = LoggingUtil.getThreemaLogger("ReConnectJobService");
+	private static boolean isStopped;
 
 	private PollingHelper pollingHelper = null;
 
 	@Override
 	public boolean onStartJob(final JobParameters jobParameters) {
+		logger.info("Reconnect job started");
+
+		isStopped = false;
+
 		new Thread(new Runnable() {
 			@Override
 			public void run() {
@@ -46,8 +51,13 @@ public class ReConnectJobService extends JobService {
 					pollingHelper = new PollingHelper(ReConnectJobService.this, "reconnectJobService");
 				}
 
-				boolean success = pollingHelper.poll(true) || (ThreemaApplication.getMasterKey() != null && ThreemaApplication.getMasterKey().isLocked());
-				jobFinished(jobParameters, !success);
+				if (!isStopped) {
+					boolean success = pollingHelper.poll(true) || (ThreemaApplication.getMasterKey() != null && ThreemaApplication.getMasterKey().isLocked());
+
+					if (!isStopped) {
+						jobFinished(jobParameters, !success);
+					}
+				}
 			}
 		}, "ReConnectJobService").start();
 
@@ -56,6 +66,9 @@ public class ReConnectJobService extends JobService {
 
 	@Override
 	public boolean onStopJob(JobParameters jobParameters) {
+		isStopped = true;
+		logger.info("Reconnect job stopped");
+
 		return false;
 	}
 }

+ 2 - 2
app/src/main/java/ch/threema/app/mediaattacher/MediaAttachActivity.java

@@ -107,7 +107,6 @@ public class MediaAttachActivity extends MediaSelectionBaseActivity implements V
 	private static final int CONTACT_PICKER_INTENT = 33002;
 	private static final int LOCATION_PICKER_INTENT = 33003;
 
-	private static final int PERMISSION_REQUEST_LOCATION = 1;
 	private static final int PERMISSION_REQUEST_ATTACH_CONTACT = 2;
 	private static final int PERMISSION_REQUEST_QR_READER = 3;
 	private static final int PERMISSION_REQUEST_ATTACH_FROM_EXTERNAL_CAMERA = 6;
@@ -316,6 +315,7 @@ public class MediaAttachActivity extends MediaSelectionBaseActivity implements V
 				editButton.setLabelText(R.string.edit);
 			}
 			selectCounterButton.setText(String.format(LocaleUtil.getCurrentLocale(this), "%d", count));
+			selectCounterButton.setContentDescription(getResources().getQuantityString(R.plurals.selection_counter_label, count, count));
 
 		} else if (BottomSheetBehavior.from(bottomSheetLayout).getState() == STATE_EXPANDED) {
 			controlPanel.animate().translationY(
@@ -721,7 +721,7 @@ public class MediaAttachActivity extends MediaSelectionBaseActivity implements V
 
 		Cursor cursor = this.getContentResolver().query(contactUri, null, null, null, null);
 		if (cursor != null && cursor.moveToFirst() && cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY) >= 0) {
-			String lookupKey = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY));
+			@SuppressLint("Range") String lookupKey = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY));
 			cursor.close();
 
 			controlPanel.setVisibility(View.GONE);

+ 1 - 0
app/src/main/java/ch/threema/app/mediaattacher/MediaSelectionActivity.java

@@ -169,6 +169,7 @@ public class MediaSelectionActivity extends MediaSelectionBaseActivity {
 		this.cancelButton = selectPanel.findViewById(R.id.cancel);
 		this.selectButton = selectPanel.findViewById(R.id.select);
 		this.selectCounterButton = selectPanel.findViewById(R.id.select_counter_button);
+		this.selectCounterButton.setContentDescription(getResources().getQuantityString(R.plurals.selection_counter_label, 0, 0));
 	}
 
 	public void setupControlPanelListeners(){

+ 5 - 1
app/src/main/java/ch/threema/app/mediaattacher/MediaSelectionBaseActivity.java

@@ -748,7 +748,11 @@ abstract public class MediaSelectionBaseActivity extends ThreemaActivity impleme
 		Animation animation;
 
 		if (bottomSheetBehavior.getState() != state) {
-			bottomSheetBehavior.setState(state);
+			try {
+				bottomSheetBehavior.setState(state);
+			} catch (IllegalArgumentException e) {
+				// some states such as DRAGGING cannot be set externally
+			}
 		}
 
 		switch (state) {

+ 2 - 0
app/src/main/java/ch/threema/app/preference/SettingsAboutFragment.kt

@@ -74,6 +74,8 @@ class SettingsAboutFragment : ThreemaPreferenceFragment() {
         initTranslatorPref()
     }
 
+    override fun getPreferenceTitleResource(): Int = R.string.menu_about
+
     override fun getPreferenceResource(): Int = R.xml.preference_about
 
     private fun initLicensePref() {

+ 17 - 27
app/src/main/java/ch/threema/app/preference/SettingsActivity.kt

@@ -27,20 +27,19 @@ import android.view.MenuItem
 import android.view.View
 import androidx.annotation.StringRes
 import androidx.fragment.app.Fragment
-import androidx.lifecycle.DefaultLifecycleObserver
-import androidx.lifecycle.LifecycleOwner
 import androidx.preference.Preference
 import androidx.preference.PreferenceFragmentCompat
 import ch.threema.app.R
 import ch.threema.app.activities.ThreemaToolbarActivity
 import ch.threema.app.utils.ConfigUtils
 import ch.threema.app.utils.ConfigUtils.THEME_DARK
+import ch.threema.app.utils.ConfigUtils.isTabletLayout
 import ch.threema.base.utils.LoggingUtil
 
 private val logger = LoggingUtil.getThreemaLogger("SettingsActivity")
 
 class SettingsActivity : ThreemaToolbarActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
-    private val settingsFragment = SettingsFragment(this::setupActionBar)
+    private val settingsFragment = SettingsFragment()
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -48,17 +47,21 @@ class SettingsActivity : ThreemaToolbarActivity(), PreferenceFragmentCompat.OnPr
         // hide contents in app switcher and inhibit screenshots
         ConfigUtils.setScreenshotsAllowed(this, preferenceService, lockAppService)
 
-        if (ConfigUtils.isTabletLayout() && ConfigUtils.getAppTheme(this) == THEME_DARK) {
+        if (isTabletLayout() && ConfigUtils.getAppTheme(this) == THEME_DARK) {
             findViewById<View>(R.id.settings_separator).visibility = View.INVISIBLE
         }
 
         if (savedInstanceState == null) {
             when {
-                intent.extras?.get(EXTRA_SHOW_NOTIFICATION_FRAGMENT) == true -> showSpecificSettings(SettingsNotificationsFragment(), R.string.prefs_notifications)
-                intent.extras?.get(EXTRA_SHOW_MEDIA_FRAGMENT) == true -> showSpecificSettings(SettingsMediaFragment(), R.string.prefs_media_title)
-                intent.extras?.get(EXTRA_SHOW_SECURITY_FRAGMENT) == true -> showSpecificSettings(SettingsSecurityFragment(), R.string.prefs_security)
+                intent.extras?.get(EXTRA_SHOW_NOTIFICATION_FRAGMENT) == true -> showSpecificSettings(SettingsNotificationsFragment())
+                intent.extras?.get(EXTRA_SHOW_MEDIA_FRAGMENT) == true -> showSpecificSettings(SettingsMediaFragment())
+                intent.extras?.get(EXTRA_SHOW_SECURITY_FRAGMENT) == true -> showSpecificSettings(SettingsSecurityFragment())
                 else -> showDefaultSettings()
             }
+        } else if (isTabletLayout()) {
+            // Remove and recreate fragments on tablets because they are not attached to the activity anymore
+            supportFragmentManager.fragments.forEach { supportFragmentManager.beginTransaction().remove(it).commit() }
+            showDefaultSettings()
         }
     }
 
@@ -73,7 +76,7 @@ class SettingsActivity : ThreemaToolbarActivity(), PreferenceFragmentCompat.OnPr
                 .commit()
 
         // Show first preference screen (privacy) on the right side on tablets per default.
-        if (ConfigUtils.isTabletLayout()) {
+        if (isTabletLayout()) {
             supportFragmentManager
                     .beginTransaction()
                     .replace(R.id.settings_detailed, SettingsPrivacyFragment())
@@ -88,11 +91,10 @@ class SettingsActivity : ThreemaToolbarActivity(), PreferenceFragmentCompat.OnPr
      * settings. This is done with an intent filter via [SettingsMediaDummyActivity] and [SettingsNotificationsDummyActivity].
      *
      * @param fragment the fragment that should be shown (directly)
-     * @param actionBarTitle the title that is shown (on phone layouts)
      */
-    private fun showSpecificSettings(fragment: Fragment, @StringRes actionBarTitle: Int) {
+    private fun showSpecificSettings(fragment: Fragment) {
         // Show first preference screen (privacy) on the right side on tablets per default.
-        if (ConfigUtils.isTabletLayout()) {
+        if (isTabletLayout()) {
             supportFragmentManager
                     .beginTransaction()
                     .replace(R.id.settings, settingsFragment)
@@ -107,8 +109,6 @@ class SettingsActivity : ThreemaToolbarActivity(), PreferenceFragmentCompat.OnPr
                     .beginTransaction()
                     .replace(R.id.settings, fragment)
                     .commit()
-
-            setupActionBar(actionBarTitle)
         }
     }
 
@@ -122,32 +122,22 @@ class SettingsActivity : ThreemaToolbarActivity(), PreferenceFragmentCompat.OnPr
                 classLoader,
                 fragmentClassName)
 
-        val layoutID = if (ConfigUtils.isTabletLayout()) R.id.settings_detailed else R.id.settings
+        val layoutID = if (isTabletLayout()) R.id.settings_detailed else R.id.settings
         val transaction = supportFragmentManager
                 .beginTransaction()
                 .replace(layoutID, fragment)
         // On tablets there is no need to add the fragment to the back stack except for nested fragments (i.e. troubleshooting)
-        if (!ConfigUtils.isTabletLayout() || fragment is SettingsTroubleshootingFragment) {
+        if (!isTabletLayout() || fragment is SettingsTroubleshootingFragment) {
             transaction.addToBackStack(null)
         }
         transaction.commit()
 
-        if (!ConfigUtils.isTabletLayout()) {
-            fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {
-                // When the fragment is resumed, the correct title is set. This is needed for nested
-                // preference fragments such as the about fragment (when coming back from troubleshooting)
-                override fun onResume(owner: LifecycleOwner) {
-                    supportActionBar?.title = pref.title
-                }
-            })
-        }
-
         settingsFragment.onPrefClicked(pref.key)
 
         return true
     }
 
-    override fun getLayoutResource() = if (ConfigUtils.isTabletLayout()) R.layout.activity_settings_tablet else R.layout.activity_settings
+    override fun getLayoutResource() = if (isTabletLayout()) R.layout.activity_settings_tablet else R.layout.activity_settings
 
     override fun onOptionsItemSelected(item: MenuItem): Boolean {
         when (item.itemId) {
@@ -166,7 +156,7 @@ class SettingsActivity : ThreemaToolbarActivity(), PreferenceFragmentCompat.OnPr
         }
     }
 
-    private fun setupActionBar(@StringRes title: Int = R.string.menu_settings) {
+    fun setActionBarTitle(@StringRes title: Int = R.string.menu_settings) {
         supportActionBar?.setDisplayHomeAsUpEnabled(true)
         supportActionBar?.setTitle(title)
     }

+ 2 - 0
app/src/main/java/ch/threema/app/preference/SettingsAppearanceFragment.kt

@@ -82,6 +82,8 @@ class SettingsAppearanceFragment : ThreemaPreferenceFragment() {
         }
     }
 
+    override fun getPreferenceTitleResource(): Int = R.string.prefs_header_appearance
+
     override fun getPreferenceResource(): Int = R.xml.preference_appearance
 
     private fun initDefaultColoredAvatarPref() {

+ 2 - 0
app/src/main/java/ch/threema/app/preference/SettingsCallsFragment.kt

@@ -46,6 +46,8 @@ class SettingsCallsFragment : ThreemaPreferenceFragment() {
         initEnableCallRejectPref()
     }
 
+    override fun getPreferenceTitleResource(): Int = R.string.prefs_title_voip
+
     override fun getPreferenceResource(): Int = R.xml.preference_calls
 
     private fun initWorkRestrictions() {

+ 2 - 0
app/src/main/java/ch/threema/app/preference/SettingsChatFragment.kt

@@ -25,5 +25,7 @@ import ch.threema.app.R
 
 @Suppress("unused")
 class SettingsChatFragment : ThreemaPreferenceFragment() {
+    override fun getPreferenceTitleResource(): Int = R.string.prefs_chatdisplay
+
     override fun getPreferenceResource() = R.xml.preference_chat
 }

+ 5 - 0
app/src/main/java/ch/threema/app/preference/SettingsDeveloperFragment.java

@@ -289,6 +289,11 @@ public class SettingsDeveloperFragment extends ThreemaPreferenceFragment {
 		}
 	}
 
+	@Override
+	public int getPreferenceTitleResource() {
+		return R.string.prefs_developers;
+	}
+
 	@Override
 	public int getPreferenceResource() {
 		return R.xml.preference_developers;

+ 10 - 15
app/src/main/java/ch/threema/app/preference/SettingsFragment.kt

@@ -33,26 +33,19 @@ import androidx.recyclerview.widget.RecyclerView
 import ch.threema.app.R
 import ch.threema.app.activities.WorkExplainActivity
 import ch.threema.app.utils.AppRestrictionUtil
-import ch.threema.app.utils.ConfigUtils
-import ch.threema.app.utils.ConfigUtils.THEME_DARK
+import ch.threema.app.utils.ConfigUtils.*
 
-class SettingsFragment(private val onResumeCallback: () -> Unit = {}) : ThreemaPreferenceFragment() {
+class SettingsFragment() : ThreemaPreferenceFragment() {
     private var preferencePairs: List<Pair<Preference, String>> = listOf()
     private var selectedPrefView: View? = null
     private val preferenceService = requirePreferenceService()
 
-    override fun onResume() {
-        super.onResume()
-
-        onResumeCallback()
-    }
-
     override fun initializePreferences() {
 
         val preferenceScreen = getPrefOrNull<PreferenceScreen>("pref_screen_header") ?: return
 
-        var voipDisabled = ConfigUtils.isBlackBerry()
-        if (!voipDisabled && ConfigUtils.isWorkRestricted()) {
+        var voipDisabled = isBlackBerry()
+        if (!voipDisabled && isWorkRestricted()) {
             val disableCalls = AppRestrictionUtil.getBooleanRestriction(getString(R.string.restriction__disable_calls))
             voipDisabled = disableCalls != null && disableCalls
         }
@@ -68,7 +61,7 @@ class SettingsFragment(private val onResumeCallback: () -> Unit = {}) : ThreemaP
         }
 
         val workPref = getPref<Preference>("pref_key_work")
-        if (!ConfigUtils.isWorkBuild()) {
+        if (!isWorkBuild()) {
             workPref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
                 startActivity(Intent(requireActivity(), WorkExplainActivity::class.java))
                 true
@@ -88,7 +81,7 @@ class SettingsFragment(private val onResumeCallback: () -> Unit = {}) : ThreemaP
             requireActivity().intent.extras?.get(SettingsActivity.EXTRA_SHOW_SECURITY_FRAGMENT) == true -> "pref_key_security"
             else -> "pref_key_privacy"
         }
-        if (ConfigUtils.isTabletLayout()) {
+        if (isTabletLayout()) {
             Handler(Looper.getMainLooper()).also {
                 it.post(object : Runnable {
                     override fun run() {
@@ -110,8 +103,8 @@ class SettingsFragment(private val onResumeCallback: () -> Unit = {}) : ThreemaP
      * @return true if the preference background could be successfully set (or if a single pane is used), false otherwise
      */
     fun onPrefClicked(prefKey: String): Boolean {
-        if (ConfigUtils.isTabletLayout()) {
-            val dark = ConfigUtils.getAppTheme(requireContext()) == THEME_DARK
+        if (isTabletLayout() && isAdded && context != null) {
+            val dark = getAppTheme(requireContext()) == THEME_DARK
 
             selectedPrefView?.setBackgroundColor(ContextCompat.getColor(requireContext(),
                     if (dark) R.color.dark_preference_background_color else R.color.preference_background_color))
@@ -132,6 +125,8 @@ class SettingsFragment(private val onResumeCallback: () -> Unit = {}) : ThreemaP
         return true
     }
 
+    override fun getPreferenceTitleResource(): Int = R.string.menu_settings
+
     override fun getPreferenceResource(): Int = R.xml.preference_headers
 
     /**

+ 2 - 0
app/src/main/java/ch/threema/app/preference/SettingsMediaFragment.kt

@@ -51,6 +51,8 @@ class SettingsMediaFragment : ThreemaPreferenceFragment() {
         initDownloadPref()
     }
 
+    override fun getPreferenceTitleResource(): Int = R.string.prefs_media_title
+
     override fun getPreferenceResource(): Int = R.xml.preference_media
 
     private fun initAutoDownloadPref() {

+ 5 - 0
app/src/main/java/ch/threema/app/preference/SettingsNotificationsFragment.java

@@ -465,6 +465,11 @@ public class SettingsNotificationsFragment extends ThreemaPreferenceFragment imp
 		// Don't do anything on cancel
 	}
 
+	@Override
+	public int getPreferenceTitleResource() {
+		return R.string.prefs_notifications;
+	}
+
 	@Override
 	public int getPreferenceResource() {
 		return R.xml.preference_notifications;

+ 2 - 0
app/src/main/java/ch/threema/app/preference/SettingsPrivacyFragment.kt

@@ -118,6 +118,8 @@ class SettingsPrivacyFragment : ThreemaPreferenceFragment(), GenericAlertDialog.
         updateView()
     }
 
+    override fun getPreferenceTitleResource(): Int = R.string.prefs_privacy
+
     override fun getPreferenceResource(): Int = R.xml.preference_privacy
 
     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {

+ 5 - 0
app/src/main/java/ch/threema/app/preference/SettingsRateFragment.java

@@ -93,6 +93,11 @@ public class SettingsRateFragment extends ThreemaPreferenceFragment implements R
 		requireActivity().onBackPressed();
 	}
 
+	@Override
+	public int getPreferenceTitleResource() {
+		return R.string.rate_title;
+	}
+
 	@Override
 	public int getPreferenceResource() {
 		return R.xml.preference_rate;

+ 5 - 0
app/src/main/java/ch/threema/app/preference/SettingsSecurityFragment.java

@@ -628,6 +628,11 @@ public class SettingsSecurityFragment extends ThreemaPreferenceFragment implemen
 		}
 	}
 
+	@Override
+	public int getPreferenceTitleResource() {
+		return R.string.prefs_security;
+	}
+
 	@Override
 	public int getPreferenceResource() {
 		return R.xml.preference_security;

+ 17 - 0
app/src/main/java/ch/threema/app/preference/SettingsTroubleshootingFragment.java

@@ -58,6 +58,7 @@ import androidx.preference.PreferenceCategory;
 import androidx.preference.PreferenceScreen;
 import androidx.preference.TwoStatePreference;
 import ch.threema.app.BuildConfig;
+import ch.threema.app.BuildFlavor;
 import ch.threema.app.R;
 import ch.threema.app.ThreemaApplication;
 import ch.threema.app.activities.DisableBatteryOptimizationsActivity;
@@ -408,6 +409,13 @@ public class SettingsTroubleshootingFragment extends ThreemaPreferenceFragment i
 				}
 			}
 		}
+
+		if (BuildFlavor.forceThreemaPush()) {
+			PreferenceCategory preferenceCategory = findPreference("pref_key_workarounds");
+			if (preferenceCategory != null) {
+				preferenceScreen.removePreference(preferenceCategory);
+			}
+		}
 	}
 
 	private void updatePowerManagerPrefs() {
@@ -543,6 +551,10 @@ public class SettingsTroubleshootingFragment extends ThreemaPreferenceFragment i
 
 	@Override
 	public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+		if (BuildFlavor.forceThreemaPush()) {
+			return;
+		}
+
 		if (key.equals(getString(R.string.preferences__threema_push_switch))) {
 			boolean newValue = sharedPreferences.getBoolean(getString(R.string.preferences__threema_push_switch), false);
 
@@ -727,6 +739,11 @@ public class SettingsTroubleshootingFragment extends ThreemaPreferenceFragment i
 	public void onCancel(String tag, Object object) {
 	}
 
+	@Override
+	protected int getPreferenceTitleResource() {
+		return R.string.prefs_troubleshooting;
+	}
+
 	@Override
 	public int getPreferenceResource() {
 		return R.xml.preference_troubleshooting;

+ 24 - 4
app/src/main/java/ch/threema/app/preference/ThreemaPreferenceFragment.kt

@@ -26,9 +26,11 @@ import androidx.annotation.StringRes
 import androidx.annotation.XmlRes
 import androidx.preference.Preference
 import androidx.preference.PreferenceFragmentCompat
+import ch.threema.app.R
 import ch.threema.app.ThreemaApplication
 import ch.threema.app.services.*
 import ch.threema.app.services.license.LicenseService
+import ch.threema.app.utils.ConfigUtils
 import ch.threema.base.utils.LoggingUtil
 
 private val logger = LoggingUtil.getThreemaLogger("ThreemaPreferenceFragment")
@@ -44,6 +46,22 @@ abstract class ThreemaPreferenceFragment : PreferenceFragmentCompat() {
         initializePreferences()
     }
 
+    override fun onResume() {
+        super.onResume()
+
+        activity.also {
+            if (it is SettingsActivity) {
+                it.setActionBarTitle(if (ConfigUtils.isTabletLayout()) R.string.menu_settings else getPreferenceTitleResource())
+            }
+        }
+    }
+
+    /**
+     * This method must be overridden to provide the action bar title of the preference category.
+     */
+    @StringRes
+    protected abstract fun getPreferenceTitleResource(): Int
+
     /**
      * This method must be overridden to provide the xml definition of the preferences.
      */
@@ -60,7 +78,8 @@ abstract class ThreemaPreferenceFragment : PreferenceFragmentCompat() {
     /**
      * Get the preference with the given key. Returns null if there is no such preference.
      */
-    protected fun <T : Preference> getPrefOrNull(@StringRes stringRes: Int): T? = getPrefOrNull(getString(stringRes))
+    protected fun <T : Preference> getPrefOrNull(@StringRes stringRes: Int): T? =
+        getPrefOrNull(getString(stringRes))
 
     /**
      * Get the preference with the given key. Returns null if there is no such preference.
@@ -77,13 +96,14 @@ abstract class ThreemaPreferenceFragment : PreferenceFragmentCompat() {
     /**
      * Get the preference with the given key. Throws an [IllegalArgumentException] if there is no such preference.
      */
-    protected fun <T : Preference> getPref(@StringRes stringRes: Int) = getPref<T>(getString(stringRes))
+    protected fun <T : Preference> getPref(@StringRes stringRes: Int) =
+        getPref<T>(getString(stringRes))
 
     /**
      * Get the preference with the given key. Throws an [IllegalArgumentException] if there is no such preference.
      */
-    protected fun <T : Preference> getPref(string: String): T = findPreference(string)
-            ?: preferenceNotFound(string)
+    protected fun <T : Preference> getPref(string: String): T =
+        findPreference(string) ?: preferenceNotFound(string)
 
     protected fun requirePreferenceService(): PreferenceService {
         ThreemaApplication.getServiceManager()?.preferenceService?.let {

+ 1 - 1
app/src/main/java/ch/threema/app/services/AvatarCacheServiceImpl.java

@@ -324,7 +324,7 @@ final public class AvatarCacheServiceImpl implements AvatarCacheService {
 
 		@Override
 		int getHashCode() {
-			if (model != null) {
+			if (model != null && model.getIdentity() != null) {
 				return model.getIdentity().hashCode();
 			}
 			return -1;

+ 1 - 1
app/src/main/java/ch/threema/app/services/LocaleServiceImpl.java

@@ -118,7 +118,7 @@ public class LocaleServiceImpl implements LocaleService {
 		Phonenumber.PhoneNumber parsedPhoneNumber;
 		try {
 			parsedPhoneNumber = this.getPhoneNumberUtil().parse(phoneNumber, this.getCountryIsoCode());
-		} catch (NumberParseException e) {
+		} catch (NumberParseException | IllegalStateException e) {
 			return null;
 		}
 

+ 9 - 3
app/src/main/java/ch/threema/app/services/MessageService.java

@@ -37,12 +37,12 @@ import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 import ch.threema.app.messagereceiver.MessageReceiver;
 import ch.threema.app.ui.MediaItem;
+import ch.threema.base.ProgressListener;
 import ch.threema.base.ThreemaException;
-import ch.threema.domain.protocol.csp.messages.AbstractGroupMessage;
-import ch.threema.domain.protocol.csp.messages.AbstractMessage;
 import ch.threema.domain.models.MessageId;
 import ch.threema.domain.protocol.csp.connection.MessageTooLongException;
-import ch.threema.base.ProgressListener;
+import ch.threema.domain.protocol.csp.messages.AbstractGroupMessage;
+import ch.threema.domain.protocol.csp.messages.AbstractMessage;
 import ch.threema.localcrypto.MasterKey;
 import ch.threema.storage.models.AbstractMessageModel;
 import ch.threema.storage.models.DistributionListMessageModel;
@@ -139,6 +139,9 @@ public interface MessageService {
 	AbstractMessageModel sendMedia(@NonNull List<MediaItem> mediaItems, @NonNull List<MessageReceiver> messageReceivers, @Nullable MessageServiceImpl.SendResultListener sendResultListener);
 
 	boolean sendUserAcknowledgement(AbstractMessageModel messageModel);
+
+	boolean sendUserAcknowledgement(AbstractMessageModel messageModel, boolean markAsRead);
+
 	boolean sendUserDecline(AbstractMessageModel messageModel);
 
 	boolean sendProfilePicture(MessageReceiver[] messageReceivers);
@@ -146,6 +149,9 @@ public interface MessageService {
 	void resendMessage(AbstractMessageModel messageModel, MessageReceiver receiver, CompletionHandler completionHandler) throws Exception;
 
 	AbstractMessageModel sendBallotMessage(BallotModel ballotModel) throws MessageTooLongException;
+
+	boolean sendUserDecline(AbstractMessageModel messageModel, boolean markAsRead);
+
 	void updateMessageState(final MessageId apiMessageId, final String identity, MessageState state, Date stateDate);
 	void updateMessageStateAtOutboxed(final MessageId apiMessageId, MessageState state, Date stateDate);
 	boolean markAsRead(AbstractMessageModel message, boolean silent) throws ThreemaException;

+ 45 - 7
app/src/main/java/ch/threema/app/services/MessageServiceImpl.java

@@ -21,6 +21,7 @@
 
 package ch.threema.app.services;
 
+import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.content.ActivityNotFoundException;
 import android.content.ClipData;
@@ -181,6 +182,7 @@ import ch.threema.storage.models.data.media.VideoDataModel;
 import ch.threema.storage.models.data.status.VoipStatusDataModel;
 
 import static ch.threema.app.ThreemaApplication.MAX_BLOB_SIZE;
+import static ch.threema.app.ThreemaApplication.MAX_BLOB_SIZE_MB;
 import static ch.threema.app.services.PreferenceService.ImageScale_DEFAULT;
 import static ch.threema.app.ui.MediaItem.TIME_UNDEFINED;
 import static ch.threema.app.ui.MediaItem.TYPE_FILE;
@@ -859,11 +861,20 @@ public class MessageServiceImpl implements MessageService {
 
 	@Override
 	public boolean sendUserAcknowledgement(AbstractMessageModel messageModel) {
+		return sendUserAcknowledgement(messageModel, false);
+	}
+
+	@Override
+	public boolean sendUserAcknowledgement(AbstractMessageModel messageModel, boolean markAsRead) {
 		if (MessageUtil.canSendUserAcknowledge(messageModel)) {
 			DeliveryReceiptMessage receipt = new DeliveryReceiptMessage();
 			receipt.setReceiptType(ProtocolDefines.DELIVERYRECEIPT_MSGUSERACK);
 
 			try {
+				if (markAsRead) {
+					markAsRead(messageModel, true);
+				}
+
 				receipt.setReceiptMessageIds(new MessageId[]{MessageId.fromString(messageModel.getApiMessageId())});
 				receipt.setFromIdentity(identityStore.getIdentity());
 				receipt.setToIdentity(messageModel.getIdentity());
@@ -885,11 +896,20 @@ public class MessageServiceImpl implements MessageService {
 
 	@Override
 	public boolean sendUserDecline(AbstractMessageModel messageModel) {
+		return sendUserDecline(messageModel, false);
+	}
+
+	@Override
+	public boolean sendUserDecline(AbstractMessageModel messageModel, boolean markAsRead) {
 		if (MessageUtil.canSendUserDecline(messageModel)) {
 			DeliveryReceiptMessage receipt = new DeliveryReceiptMessage();
 			receipt.setReceiptType(ProtocolDefines.DELIVERYRECEIPT_MSGUSERDEC);
 
 			try {
+				if (markAsRead) {
+					markAsRead(messageModel, true);
+				}
+
 				receipt.setReceiptMessageIds(new MessageId[]{MessageId.fromString(messageModel.getApiMessageId())});
 				receipt.setFromIdentity(identityStore.getIdentity());
 				receipt.setToIdentity(messageModel.getIdentity());
@@ -1605,7 +1625,12 @@ public class MessageServiceImpl implements MessageService {
 			fireOnCreatedMessage(messageModel);
 
 			if (canDownload(MessageType.VOICEMESSAGE)) {
-				downloadMediaMessage(messageModel, null);
+				try {
+					downloadMediaMessage(messageModel, null);
+				} catch (Exception e) {
+					// a failed blob auto-download should not be considered a failure as the user can try again manually
+					logger.error("Unable to auto-download blob", e);
+				}
 			}
 		}
 		else {
@@ -1696,7 +1721,12 @@ public class MessageServiceImpl implements MessageService {
 
 				if (canDownload(MessageType.VIDEO)) {
 					if (videoSize <= FILE_AUTO_DOWNLOAD_MAX_SIZE_ISO) {
-						downloadMediaMessage(messageModel, null);
+						try {
+							downloadMediaMessage(messageModel, null);
+						} catch (Exception e) {
+							// a failed blob auto-download should not be considered a failure as the user can try again manually
+							logger.error("Unable to auto-download blob", e);
+						}
 					}
 				}
 			} else {
@@ -1772,7 +1802,12 @@ public class MessageServiceImpl implements MessageService {
 			fireOnCreatedMessage(messageModel);
 			// Auto download
 			if (canDownload(messageModel)) {
-				downloadMediaMessage(messageModel, null);
+				try {
+					downloadMediaMessage(messageModel, null);
+				} catch (Exception e) {
+					// a failed blob auto-download should not be considered a failure as the user can try again manually
+					logger.error("Unable to auto-download blob", e);
+				}
 			}
 		}
 		else {
@@ -4093,6 +4128,7 @@ public class MessageServiceImpl implements MessageService {
 		return true;
 	}
 
+	@SuppressLint("Range")
 	public @Nullable FileDataModel createFileDataModel(Context context, MediaItem mediaItem) {
 		ContentResolver contentResolver = context.getContentResolver();
 		String mimeType = mediaItem.getMimeType();
@@ -4411,8 +4447,9 @@ public class MessageServiceImpl implements MessageService {
  				int fileLength = inputStream.available();
 
 				if (fileLength > MAX_BLOB_SIZE) {
-					logger.info(context.getString(R.string.file_too_large));
-					RuntimeUtil.runOnUiThread(() -> Toast.makeText(ThreemaApplication.getAppContext(), R.string.file_too_large, Toast.LENGTH_LONG).show());
+					String errorMessage = context.getString(R.string.file_too_large, MAX_BLOB_SIZE_MB);
+					logger.info(errorMessage);
+					RuntimeUtil.runOnUiThread(() -> Toast.makeText(ThreemaApplication.getAppContext(), errorMessage, Toast.LENGTH_LONG).show());
 					return null;
 				}
 
@@ -4433,8 +4470,9 @@ public class MessageServiceImpl implements MessageService {
 						}
 
 						if (readCount > MAX_BLOB_SIZE) {
-							logger.info(context.getString(R.string.file_too_large));
-							RuntimeUtil.runOnUiThread(() -> Toast.makeText(ThreemaApplication.getAppContext(), R.string.file_too_large, Toast.LENGTH_LONG).show());
+							String errorMessage = context.getString(R.string.file_too_large, MAX_BLOB_SIZE_MB);
+							logger.info(errorMessage);
+							RuntimeUtil.runOnUiThread(() -> Toast.makeText(ThreemaApplication.getAppContext(), errorMessage, Toast.LENGTH_LONG).show());
 							return null;
 						}
 

+ 5 - 4
app/src/main/java/ch/threema/app/services/NotificationActionService.java

@@ -41,6 +41,7 @@ import ch.threema.app.ThreemaApplication;
 import ch.threema.app.managers.ServiceManager;
 import ch.threema.app.messagereceiver.MessageReceiver;
 import ch.threema.app.services.group.IncomingGroupJoinRequestService;
+import ch.threema.app.utils.ConversationNotificationUtil;
 import ch.threema.app.utils.IntentDataUtil;
 import ch.threema.app.utils.RuntimeUtil;
 import ch.threema.app.utils.TestUtil;
@@ -146,8 +147,8 @@ public class NotificationActionService extends IntentService {
 	private void ack(@NonNull AbstractMessageModel messageModel) {
 		lifetimeService.acquireConnection(TAG);
 
-		messageService.sendUserAcknowledgement(messageModel);
-		messageService.markMessageAsRead(messageModel, notificationService);
+		messageService.sendUserAcknowledgement(messageModel, true);
+		notificationService.cancelConversationNotification(ConversationNotificationUtil.getUid(messageModel));
 
 		showToast(R.string.message_acknowledged);
 
@@ -157,8 +158,8 @@ public class NotificationActionService extends IntentService {
 	private void dec(@NonNull AbstractMessageModel messageModel) {
 		lifetimeService.acquireConnection(TAG);
 
-		messageService.sendUserDecline(messageModel);
-		messageService.markMessageAsRead(messageModel, notificationService);
+		messageService.sendUserDecline(messageModel, true);
+		notificationService.cancelConversationNotification(ConversationNotificationUtil.getUid(messageModel));
 
 		showToast(R.string.message_declined);
 

+ 2 - 1
app/src/main/java/ch/threema/app/services/NotificationServiceImpl.java

@@ -1407,6 +1407,7 @@ public class NotificationServiceImpl implements NotificationService {
 						this.cancelAndDestroyConversationNotification(conversationNotification);
 					}
 				}
+				showIconBadge(conversationNotifications.size());
 			}
 		}
 		this.cancel(ThreemaApplication.NEW_MESSAGE_NOTIFICATION_ID);
@@ -1677,7 +1678,7 @@ public class NotificationServiceImpl implements NotificationService {
 			NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender();
 			wearableExtender.addAction(tryAgainAction);
 
-			String content = String.format(this.context.getString(R.string.sending_message_failed), num);
+			String content = this.context.getResources().getQuantityString(R.plurals.sending_message_failed, num, num);
 
 			NotificationCompat.Builder builder =
 				new NotificationBuilderWrapper(context, NOTIFICATION_CHANNEL_ALERT, null)

+ 1 - 4
app/src/main/java/ch/threema/app/ui/DirectoryDataSource.java

@@ -37,7 +37,6 @@ import ch.threema.app.managers.ServiceManager;
 import ch.threema.app.services.PreferenceService;
 import ch.threema.app.stores.IdentityStore;
 import ch.threema.app.utils.RuntimeUtil;
-import ch.threema.app.utils.TestUtil;
 import ch.threema.base.utils.LoggingUtil;
 import ch.threema.domain.protocol.api.APIConnector;
 import ch.threema.domain.protocol.api.work.WorkDirectory;
@@ -83,9 +82,7 @@ public class DirectoryDataSource extends PageKeyedDataSource<WorkDirectory, Work
 	public void loadInitial(@NonNull LoadInitialParams<WorkDirectory> params, @NonNull LoadInitialCallback<WorkDirectory, WorkDirectoryContact> callback) {
 		logger.debug("*** loadInitial");
 
-		if (!TestUtil.empty(queryText)) {
-			fetchInitialData(callback);
-		}
+		fetchInitialData(callback);
 	}
 
 	@Override

+ 99 - 0
app/src/main/java/ch/threema/app/ui/ReportSpamView.kt

@@ -0,0 +1,99 @@
+/*  _____ _
+ * |_   _| |_  _ _ ___ ___ _ __  __ _
+ *   | | | ' \| '_/ -_) -_) '  \/ _` |_
+ *   |_| |_||_|_| \___\___|_|_|_\__,_(_)
+ *
+ * Threema for Android
+ * Copyright (c) 2014-2022 Threema GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package ch.threema.app.ui
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import androidx.annotation.UiThread
+import androidx.appcompat.app.AppCompatActivity
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.lifecycle.DefaultLifecycleObserver
+import ch.threema.app.R
+import ch.threema.app.dialogs.TextWithCheckboxDialog
+import ch.threema.app.utils.NameUtil
+import ch.threema.storage.models.ContactModel
+import com.google.android.material.chip.Chip
+
+class ReportSpamView : ConstraintLayout, DefaultLifecycleObserver {
+    private var listener: OnReportButtonClickListener? = null
+    private var contactModel: ContactModel? = null
+
+    constructor(context: Context) : super(context) {
+        init(context)
+    }
+
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
+        init(context)
+    }
+
+    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
+        init(context)
+    }
+
+    private fun init(context: Context) {
+        if (getContext() !is AppCompatActivity) {
+            return
+        }
+        activity.lifecycle.addObserver(this)
+        val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+        inflater.inflate(R.layout.view_report_spam, this)
+    }
+
+    fun setListener(listener: OnReportButtonClickListener?) {
+        this.listener = listener
+    }
+
+    @UiThread
+    fun show(contactModel: ContactModel) {
+        this.contactModel = contactModel
+        if (visibility != VISIBLE) {
+            visibility = VISIBLE
+        }
+    }
+
+    @UiThread
+    fun hide() {
+        if (visibility != GONE) {
+            visibility = GONE
+        }
+    }
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        val reportChip: Chip = findViewById(R.id.chip_report_spam)
+        reportChip.setOnClickListener { v: View? ->
+            val dialog = TextWithCheckboxDialog.newInstance(context.getString(R.string.spam_report_dialog_title, NameUtil.getDisplayNameOrNickname(contactModel, true)), R.string.spam_report_dialog_explain,
+                    R.string.spam_report_dialog_block_checkbox, R.string.spam_report_short, R.string.cancel)
+            dialog.setCallback { tag: String?, data: Any?, checked: Boolean -> listener!!.onReportSpamClicked(contactModel!!, checked) }
+            dialog.show(activity.supportFragmentManager, "")
+        }
+    }
+
+    private val activity: AppCompatActivity
+        get() = context as AppCompatActivity
+
+    interface OnReportButtonClickListener {
+        fun onReportSpamClicked(spammerContactModel: ContactModel, block: Boolean)
+    }
+}

+ 8 - 0
app/src/main/java/ch/threema/app/utils/ConfigUtils.java

@@ -24,6 +24,7 @@ package ch.threema.app.utils;
 import android.Manifest;
 import android.annotation.SuppressLint;
 import android.app.Activity;
+import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -1464,4 +1465,11 @@ public class ConfigUtils {
 			contentResolver.applyBatch(authority, new ArrayList<>(contentProviderOperationsBatch));
 		}
 	}
+
+	public static void clearAppData(Context context) {
+		ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+		if (manager != null) {
+			manager.clearApplicationUserData();
+		}
+	}
 }

+ 790 - 0
app/src/main/java/ch/threema/app/utils/UnicodeUtil.kt

@@ -0,0 +1,790 @@
+/*  _____ _
+ * |_   _| |_  _ _ ___ ___ _ __  __ _
+ *   | | | ' \| '_/ -_) -_) '  \/ _` |_
+ *   |_| |_||_|_| \___\___|_|_|_\__,_(_)
+ *
+ * Threema for Android
+ * Copyright (c) 2022 Threema GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package ch.threema.app.utils
+
+import java.util.*
+
+/**
+ * Provides unicode script functionality that is not available before SDK version 24.
+ * From SDK version 24 the implementation of the Character class can be used.
+ */
+object UnicodeUtil {
+
+    fun getScript(c: Char): UnicodeScript = getScriptForCodepoint(c.code)
+
+    private fun getScriptForCodepoint(codepoint: Int): UnicodeScript {
+        if (!Character.isValidCodePoint(codepoint)) {
+            throw IllegalArgumentException("Invalid code point $codepoint")
+        }
+        if (Character.getType(codepoint) == Character.UNASSIGNED.toInt()) {
+            return UnicodeScript.UNKNOWN
+        }
+        val position = Arrays.binarySearch(scriptStartOffsets, codepoint)
+        return scripts[if (position < 0) -position - 2 else position]
+    }
+
+    enum class UnicodeScript {
+        COMMON,
+        LATIN,
+        GREEK,
+        CYRILLIC,
+        ARMENIAN,
+        HEBREW,
+        ARABIC,
+        SYRIAC,
+        THAANA,
+        DEVANAGARI,
+        BENGALI,
+        GURMUKHI,
+        GUJARATI,
+        ORIYA,
+        TAMIL,
+        TELUGU,
+        KANNADA,
+        MALAYALAM,
+        SINHALA,
+        THAI,
+        LAO,
+        TIBETAN,
+        MYANMAR,
+        GEORGIAN,
+        HANGUL,
+        ETHIOPIC,
+        CHEROKEE,
+        CANADIAN_ABORIGINAL,
+        OGHAM,
+        RUNIC,
+        KHMER,
+        MONGOLIAN,
+        HIRAGANA,
+        KATAKANA,
+        BOPOMOFO,
+        HAN,
+        YI,
+        OLD_ITALIC,
+        GOTHIC,
+        DESERET,
+        INHERITED,
+        TAGALOG,
+        HANUNOO,
+        BUHID,
+        TAGBANWA,
+        LIMBU,
+        TAI_LE,
+        LINEAR_B,
+        UGARITIC,
+        SHAVIAN,
+        OSMANYA,
+        CYPRIOT,
+        BRAILLE,
+        BUGINESE,
+        COPTIC,
+        NEW_TAI_LUE,
+        GLAGOLITIC,
+        TIFINAGH,
+        SYLOTI_NAGRI,
+        OLD_PERSIAN,
+        KHAROSHTHI,
+        BALINESE,
+        CUNEIFORM,
+        PHOENICIAN,
+        PHAGS_PA,
+        NKO,
+        SUNDANESE,
+        BATAK,
+        LEPCHA,
+        OL_CHIKI,
+        VAI,
+        SAURASHTRA,
+        KAYAH_LI,
+        REJANG,
+        LYCIAN,
+        CARIAN,
+        LYDIAN,
+        CHAM,
+        TAI_THAM,
+        TAI_VIET,
+        AVESTAN,
+        EGYPTIAN_HIEROGLYPHS,
+        SAMARITAN,
+        MANDAIC,
+        LISU,
+        BAMUM,
+        JAVANESE,
+        MEETEI_MAYEK,
+        IMPERIAL_ARAMAIC,
+        OLD_SOUTH_ARABIAN,
+        INSCRIPTIONAL_PARTHIAN,
+        INSCRIPTIONAL_PAHLAVI,
+        OLD_TURKIC,
+        BRAHMI,
+        KAITHI,
+        MEROITIC_HIEROGLYPHS,
+        MEROITIC_CURSIVE,
+        SORA_SOMPENG,
+        CHAKMA,
+        SHARADA,
+        TAKRI,
+        MIAO,
+        UNKNOWN
+    }
+
+    private val scriptStartOffsets = intArrayOf(
+        0x0000,  // COMMON
+        0x0041,  // LATIN
+        0x005B,  // COMMON
+        0x0061,  // LATIN
+        0x007B,  // COMMON
+        0x00AA,  // LATIN
+        0x00AB,  // COMMON
+        0x00BA,  // LATIN
+        0x00BB,  // COMMON
+        0x00C0,  // LATIN
+        0x00D7,  // COMMON
+        0x00D8,  // LATIN
+        0x00F7,  // COMMON
+        0x00F8,  // LATIN
+        0x02B9,  // COMMON
+        0x02E0,  // LATIN
+        0x02E5,  // COMMON
+        0x02EA,  // BOPOMOFO
+        0x02EC,  // COMMON
+        0x0300,  // INHERITED
+        0x0370,  // GREEK
+        0x0374,  // COMMON
+        0x0375,  // GREEK
+        0x037E,  // COMMON
+        0x0384,  // GREEK
+        0x0385,  // COMMON
+        0x0386,  // GREEK
+        0x0387,  // COMMON
+        0x0388,  // GREEK
+        0x03E2,  // COPTIC
+        0x03F0,  // GREEK
+        0x0400,  // CYRILLIC
+        0x0485,  // INHERITED
+        0x0487,  // CYRILLIC
+        0x0531,  // ARMENIAN
+        0x0589,  // COMMON
+        0x058A,  // ARMENIAN
+        0x0591,  // HEBREW
+        0x0600,  // ARABIC
+        0x060C,  // COMMON
+        0x060D,  // ARABIC
+        0x061B,  // COMMON
+        0x061E,  // ARABIC
+        0x061F,  // COMMON
+        0x0620,  // ARABIC
+        0x0640,  // COMMON
+        0x0641,  // ARABIC
+        0x064B,  // INHERITED
+        0x0656,  // ARABIC
+        0x0660,  // COMMON
+        0x066A,  // ARABIC
+        0x0670,  // INHERITED
+        0x0671,  // ARABIC
+        0x06DD,  // COMMON
+        0x06DE,  // ARABIC
+        0x0700,  // SYRIAC
+        0x0750,  // ARABIC
+        0x0780,  // THAANA
+        0x07C0,  // NKO
+        0x0800,  // SAMARITAN
+        0x0840,  // MANDAIC
+        0x08A0,  // ARABIC
+        0x0900,  // DEVANAGARI
+        0x0951,  // INHERITED
+        0x0953,  // DEVANAGARI
+        0x0964,  // COMMON
+        0x0966,  // DEVANAGARI
+        0x0981,  // BENGALI
+        0x0A01,  // GURMUKHI
+        0x0A81,  // GUJARATI
+        0x0B01,  // ORIYA
+        0x0B82,  // TAMIL
+        0x0C01,  // TELUGU
+        0x0C82,  // KANNADA
+        0x0D02,  // MALAYALAM
+        0x0D82,  // SINHALA
+        0x0E01,  // THAI
+        0x0E3F,  // COMMON
+        0x0E40,  // THAI
+        0x0E81,  // LAO
+        0x0F00,  // TIBETAN
+        0x0FD5,  // COMMON
+        0x0FD9,  // TIBETAN
+        0x1000,  // MYANMAR
+        0x10A0,  // GEORGIAN
+        0x10FB,  // COMMON
+        0x10FC,  // GEORGIAN
+        0x1100,  // HANGUL
+        0x1200,  // ETHIOPIC
+        0x13A0,  // CHEROKEE
+        0x1400,  // CANADIAN_ABORIGINAL
+        0x1680,  // OGHAM
+        0x16A0,  // RUNIC
+        0x16EB,  // COMMON
+        0x16EE,  // RUNIC
+        0x1700,  // TAGALOG
+        0x1720,  // HANUNOO
+        0x1735,  // COMMON
+        0x1740,  // BUHID
+        0x1760,  // TAGBANWA
+        0x1780,  // KHMER
+        0x1800,  // MONGOLIAN
+        0x1802,  // COMMON
+        0x1804,  // MONGOLIAN
+        0x1805,  // COMMON
+        0x1806,  // MONGOLIAN
+        0x18B0,  // CANADIAN_ABORIGINAL
+        0x1900,  // LIMBU
+        0x1950,  // TAI_LE
+        0x1980,  // NEW_TAI_LUE
+        0x19E0,  // KHMER
+        0x1A00,  // BUGINESE
+        0x1A20,  // TAI_THAM
+        0x1B00,  // BALINESE
+        0x1B80,  // SUNDANESE
+        0x1BC0,  // BATAK
+        0x1C00,  // LEPCHA
+        0x1C50,  // OL_CHIKI
+        0x1CC0,  // SUNDANESE
+        0x1CD0,  // INHERITED
+        0x1CD3,  // COMMON
+        0x1CD4,  // INHERITED
+        0x1CE1,  // COMMON
+        0x1CE2,  // INHERITED
+        0x1CE9,  // COMMON
+        0x1CED,  // INHERITED
+        0x1CEE,  // COMMON
+        0x1CF4,  // INHERITED
+        0x1CF5,  // COMMON
+        0x1D00,  // LATIN
+        0x1D26,  // GREEK
+        0x1D2B,  // CYRILLIC
+        0x1D2C,  // LATIN
+        0x1D5D,  // GREEK
+        0x1D62,  // LATIN
+        0x1D66,  // GREEK
+        0x1D6B,  // LATIN
+        0x1D78,  // CYRILLIC
+        0x1D79,  // LATIN
+        0x1DBF,  // GREEK
+        0x1DC0,  // INHERITED
+        0x1E00,  // LATIN
+        0x1F00,  // GREEK
+        0x2000,  // COMMON
+        0x200C,  // INHERITED
+        0x200E,  // COMMON
+        0x2071,  // LATIN
+        0x2074,  // COMMON
+        0x207F,  // LATIN
+        0x2080,  // COMMON
+        0x2090,  // LATIN
+        0x20A0,  // COMMON
+        0x20D0,  // INHERITED
+        0x2100,  // COMMON
+        0x2126,  // GREEK
+        0x2127,  // COMMON
+        0x212A,  // LATIN
+        0x212C,  // COMMON
+        0x2132,  // LATIN
+        0x2133,  // COMMON
+        0x214E,  // LATIN
+        0x214F,  // COMMON
+        0x2160,  // LATIN
+        0x2189,  // COMMON
+        0x2800,  // BRAILLE
+        0x2900,  // COMMON
+        0x2C00,  // GLAGOLITIC
+        0x2C60,  // LATIN
+        0x2C80,  // COPTIC
+        0x2D00,  // GEORGIAN
+        0x2D30,  // TIFINAGH
+        0x2D80,  // ETHIOPIC
+        0x2DE0,  // CYRILLIC
+        0x2E00,  // COMMON
+        0x2E80,  // HAN
+        0x2FF0,  // COMMON
+        0x3005,  // HAN
+        0x3006,  // COMMON
+        0x3007,  // HAN
+        0x3008,  // COMMON
+        0x3021,  // HAN
+        0x302A,  // INHERITED
+        0x302E,  // HANGUL
+        0x3030,  // COMMON
+        0x3038,  // HAN
+        0x303C,  // COMMON
+        0x3041,  // HIRAGANA
+        0x3099,  // INHERITED
+        0x309B,  // COMMON
+        0x309D,  // HIRAGANA
+        0x30A0,  // COMMON
+        0x30A1,  // KATAKANA
+        0x30FB,  // COMMON
+        0x30FD,  // KATAKANA
+        0x3105,  // BOPOMOFO
+        0x3131,  // HANGUL
+        0x3190,  // COMMON
+        0x31A0,  // BOPOMOFO
+        0x31C0,  // COMMON
+        0x31F0,  // KATAKANA
+        0x3200,  // HANGUL
+        0x3220,  // COMMON
+        0x3260,  // HANGUL
+        0x327F,  // COMMON
+        0x32D0,  // KATAKANA
+        0x3358,  // COMMON
+        0x3400,  // HAN
+        0x4DC0,  // COMMON
+        0x4E00,  // HAN
+        0xA000,  // YI
+        0xA4D0,  // LISU
+        0xA500,  // VAI
+        0xA640,  // CYRILLIC
+        0xA6A0,  // BAMUM
+        0xA700,  // COMMON
+        0xA722,  // LATIN
+        0xA788,  // COMMON
+        0xA78B,  // LATIN
+        0xA800,  // SYLOTI_NAGRI
+        0xA830,  // COMMON
+        0xA840,  // PHAGS_PA
+        0xA880,  // SAURASHTRA
+        0xA8E0,  // DEVANAGARI
+        0xA900,  // KAYAH_LI
+        0xA930,  // REJANG
+        0xA960,  // HANGUL
+        0xA980,  // JAVANESE
+        0xAA00,  // CHAM
+        0xAA60,  // MYANMAR
+        0xAA80,  // TAI_VIET
+        0xAAE0,  // MEETEI_MAYEK
+        0xAB01,  // ETHIOPIC
+        0xABC0,  // MEETEI_MAYEK
+        0xAC00,  // HANGUL
+        0xD7FC,  // UNKNOWN
+        0xF900,  // HAN
+        0xFB00,  // LATIN
+        0xFB13,  // ARMENIAN
+        0xFB1D,  // HEBREW
+        0xFB50,  // ARABIC
+        0xFD3E,  // COMMON
+        0xFD50,  // ARABIC
+        0xFDFD,  // COMMON
+        0xFE00,  // INHERITED
+        0xFE10,  // COMMON
+        0xFE20,  // INHERITED
+        0xFE30,  // COMMON
+        0xFE70,  // ARABIC
+        0xFEFF,  // COMMON
+        0xFF21,  // LATIN
+        0xFF3B,  // COMMON
+        0xFF41,  // LATIN
+        0xFF5B,  // COMMON
+        0xFF66,  // KATAKANA
+        0xFF70,  // COMMON
+        0xFF71,  // KATAKANA
+        0xFF9E,  // COMMON
+        0xFFA0,  // HANGUL
+        0xFFE0,  // COMMON
+        0x10000,  // LINEAR_B
+        0x10100,  // COMMON
+        0x10140,  // GREEK
+        0x10190,  // COMMON
+        0x101FD,  // INHERITED
+        0x10280,  // LYCIAN
+        0x102A0,  // CARIAN
+        0x10300,  // OLD_ITALIC
+        0x10330,  // GOTHIC
+        0x10380,  // UGARITIC
+        0x103A0,  // OLD_PERSIAN
+        0x10400,  // DESERET
+        0x10450,  // SHAVIAN
+        0x10480,  // OSMANYA
+        0x10800,  // CYPRIOT
+        0x10840,  // IMPERIAL_ARAMAIC
+        0x10900,  // PHOENICIAN
+        0x10920,  // LYDIAN
+        0x10980,  // MEROITIC_HIEROGLYPHS
+        0x109A0,  // MEROITIC_CURSIVE
+        0x10A00,  // KHAROSHTHI
+        0x10A60,  // OLD_SOUTH_ARABIAN
+        0x10B00,  // AVESTAN
+        0x10B40,  // INSCRIPTIONAL_PARTHIAN
+        0x10B60,  // INSCRIPTIONAL_PAHLAVI
+        0x10C00,  // OLD_TURKIC
+        0x10E60,  // ARABIC
+        0x11000,  // BRAHMI
+        0x11080,  // KAITHI
+        0x110D0,  // SORA_SOMPENG
+        0x11100,  // CHAKMA
+        0x11180,  // SHARADA
+        0x11680,  // TAKRI
+        0x12000,  // CUNEIFORM
+        0x13000,  // EGYPTIAN_HIEROGLYPHS
+        0x16800,  // BAMUM
+        0x16F00,  // MIAO
+        0x1B000,  // KATAKANA
+        0x1B001,  // HIRAGANA
+        0x1D000,  // COMMON
+        0x1D167,  // INHERITED
+        0x1D16A,  // COMMON
+        0x1D17B,  // INHERITED
+        0x1D183,  // COMMON
+        0x1D185,  // INHERITED
+        0x1D18C,  // COMMON
+        0x1D1AA,  // INHERITED
+        0x1D1AE,  // COMMON
+        0x1D200,  // GREEK
+        0x1D300,  // COMMON
+        0x1EE00,  // ARABIC
+        0x1F000,  // COMMON
+        0x1F200,  // HIRAGANA
+        0x1F201,  // COMMON
+        0x20000,  // HAN
+        0xE0001,  // COMMON
+        0xE0100,  // INHERITED
+        0xE01F0 // E01F0..10FFFF; UNKNOWN
+    )
+
+    private val scripts = arrayOf(
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.COMMON,
+        UnicodeScript.BOPOMOFO,
+        UnicodeScript.COMMON,
+        UnicodeScript.INHERITED,
+        UnicodeScript.GREEK,
+        UnicodeScript.COMMON,
+        UnicodeScript.GREEK,
+        UnicodeScript.COMMON,
+        UnicodeScript.GREEK,
+        UnicodeScript.COMMON,
+        UnicodeScript.GREEK,
+        UnicodeScript.COMMON,
+        UnicodeScript.GREEK,
+        UnicodeScript.COPTIC,
+        UnicodeScript.GREEK,
+        UnicodeScript.CYRILLIC,
+        UnicodeScript.INHERITED,
+        UnicodeScript.CYRILLIC,
+        UnicodeScript.ARMENIAN,
+        UnicodeScript.COMMON,
+        UnicodeScript.ARMENIAN,
+        UnicodeScript.HEBREW,
+        UnicodeScript.ARABIC,
+        UnicodeScript.COMMON,
+        UnicodeScript.ARABIC,
+        UnicodeScript.COMMON,
+        UnicodeScript.ARABIC,
+        UnicodeScript.COMMON,
+        UnicodeScript.ARABIC,
+        UnicodeScript.COMMON,
+        UnicodeScript.ARABIC,
+        UnicodeScript.INHERITED,
+        UnicodeScript.ARABIC,
+        UnicodeScript.COMMON,
+        UnicodeScript.ARABIC,
+        UnicodeScript.INHERITED,
+        UnicodeScript.ARABIC,
+        UnicodeScript.COMMON,
+        UnicodeScript.ARABIC,
+        UnicodeScript.SYRIAC,
+        UnicodeScript.ARABIC,
+        UnicodeScript.THAANA,
+        UnicodeScript.NKO,
+        UnicodeScript.SAMARITAN,
+        UnicodeScript.MANDAIC,
+        UnicodeScript.ARABIC,
+        UnicodeScript.DEVANAGARI,
+        UnicodeScript.INHERITED,
+        UnicodeScript.DEVANAGARI,
+        UnicodeScript.COMMON,
+        UnicodeScript.DEVANAGARI,
+        UnicodeScript.BENGALI,
+        UnicodeScript.GURMUKHI,
+        UnicodeScript.GUJARATI,
+        UnicodeScript.ORIYA,
+        UnicodeScript.TAMIL,
+        UnicodeScript.TELUGU,
+        UnicodeScript.KANNADA,
+        UnicodeScript.MALAYALAM,
+        UnicodeScript.SINHALA,
+        UnicodeScript.THAI,
+        UnicodeScript.COMMON,
+        UnicodeScript.THAI,
+        UnicodeScript.LAO,
+        UnicodeScript.TIBETAN,
+        UnicodeScript.COMMON,
+        UnicodeScript.TIBETAN,
+        UnicodeScript.MYANMAR,
+        UnicodeScript.GEORGIAN,
+        UnicodeScript.COMMON,
+        UnicodeScript.GEORGIAN,
+        UnicodeScript.HANGUL,
+        UnicodeScript.ETHIOPIC,
+        UnicodeScript.CHEROKEE,
+        UnicodeScript.CANADIAN_ABORIGINAL,
+        UnicodeScript.OGHAM,
+        UnicodeScript.RUNIC,
+        UnicodeScript.COMMON,
+        UnicodeScript.RUNIC,
+        UnicodeScript.TAGALOG,
+        UnicodeScript.HANUNOO,
+        UnicodeScript.COMMON,
+        UnicodeScript.BUHID,
+        UnicodeScript.TAGBANWA,
+        UnicodeScript.KHMER,
+        UnicodeScript.MONGOLIAN,
+        UnicodeScript.COMMON,
+        UnicodeScript.MONGOLIAN,
+        UnicodeScript.COMMON,
+        UnicodeScript.MONGOLIAN,
+        UnicodeScript.CANADIAN_ABORIGINAL,
+        UnicodeScript.LIMBU,
+        UnicodeScript.TAI_LE,
+        UnicodeScript.NEW_TAI_LUE,
+        UnicodeScript.KHMER,
+        UnicodeScript.BUGINESE,
+        UnicodeScript.TAI_THAM,
+        UnicodeScript.BALINESE,
+        UnicodeScript.SUNDANESE,
+        UnicodeScript.BATAK,
+        UnicodeScript.LEPCHA,
+        UnicodeScript.OL_CHIKI,
+        UnicodeScript.SUNDANESE,
+        UnicodeScript.INHERITED,
+        UnicodeScript.COMMON,
+        UnicodeScript.INHERITED,
+        UnicodeScript.COMMON,
+        UnicodeScript.INHERITED,
+        UnicodeScript.COMMON,
+        UnicodeScript.INHERITED,
+        UnicodeScript.COMMON,
+        UnicodeScript.INHERITED,
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.GREEK,
+        UnicodeScript.CYRILLIC,
+        UnicodeScript.LATIN,
+        UnicodeScript.GREEK,
+        UnicodeScript.LATIN,
+        UnicodeScript.GREEK,
+        UnicodeScript.LATIN,
+        UnicodeScript.CYRILLIC,
+        UnicodeScript.LATIN,
+        UnicodeScript.GREEK,
+        UnicodeScript.INHERITED,
+        UnicodeScript.LATIN,
+        UnicodeScript.GREEK,
+        UnicodeScript.COMMON,
+        UnicodeScript.INHERITED,
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.COMMON,
+        UnicodeScript.INHERITED,
+        UnicodeScript.COMMON,
+        UnicodeScript.GREEK,
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.COMMON,
+        UnicodeScript.BRAILLE,
+        UnicodeScript.COMMON,
+        UnicodeScript.GLAGOLITIC,
+        UnicodeScript.LATIN,
+        UnicodeScript.COPTIC,
+        UnicodeScript.GEORGIAN,
+        UnicodeScript.TIFINAGH,
+        UnicodeScript.ETHIOPIC,
+        UnicodeScript.CYRILLIC,
+        UnicodeScript.COMMON,
+        UnicodeScript.HAN,
+        UnicodeScript.COMMON,
+        UnicodeScript.HAN,
+        UnicodeScript.COMMON,
+        UnicodeScript.HAN,
+        UnicodeScript.COMMON,
+        UnicodeScript.HAN,
+        UnicodeScript.INHERITED,
+        UnicodeScript.HANGUL,
+        UnicodeScript.COMMON,
+        UnicodeScript.HAN,
+        UnicodeScript.COMMON,
+        UnicodeScript.HIRAGANA,
+        UnicodeScript.INHERITED,
+        UnicodeScript.COMMON,
+        UnicodeScript.HIRAGANA,
+        UnicodeScript.COMMON,
+        UnicodeScript.KATAKANA,
+        UnicodeScript.COMMON,
+        UnicodeScript.KATAKANA,
+        UnicodeScript.BOPOMOFO,
+        UnicodeScript.HANGUL,
+        UnicodeScript.COMMON,
+        UnicodeScript.BOPOMOFO,
+        UnicodeScript.COMMON,
+        UnicodeScript.KATAKANA,
+        UnicodeScript.HANGUL,
+        UnicodeScript.COMMON,
+        UnicodeScript.HANGUL,
+        UnicodeScript.COMMON,
+        UnicodeScript.KATAKANA,
+        UnicodeScript.COMMON,
+        UnicodeScript.HAN,
+        UnicodeScript.COMMON,
+        UnicodeScript.HAN,
+        UnicodeScript.YI,
+        UnicodeScript.LISU,
+        UnicodeScript.VAI,
+        UnicodeScript.CYRILLIC,
+        UnicodeScript.BAMUM,
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.SYLOTI_NAGRI,
+        UnicodeScript.COMMON,
+        UnicodeScript.PHAGS_PA,
+        UnicodeScript.SAURASHTRA,
+        UnicodeScript.DEVANAGARI,
+        UnicodeScript.KAYAH_LI,
+        UnicodeScript.REJANG,
+        UnicodeScript.HANGUL,
+        UnicodeScript.JAVANESE,
+        UnicodeScript.CHAM,
+        UnicodeScript.MYANMAR,
+        UnicodeScript.TAI_VIET,
+        UnicodeScript.MEETEI_MAYEK,
+        UnicodeScript.ETHIOPIC,
+        UnicodeScript.MEETEI_MAYEK,
+        UnicodeScript.HANGUL,
+        UnicodeScript.UNKNOWN,
+        UnicodeScript.HAN,
+        UnicodeScript.LATIN,
+        UnicodeScript.ARMENIAN,
+        UnicodeScript.HEBREW,
+        UnicodeScript.ARABIC,
+        UnicodeScript.COMMON,
+        UnicodeScript.ARABIC,
+        UnicodeScript.COMMON,
+        UnicodeScript.INHERITED,
+        UnicodeScript.COMMON,
+        UnicodeScript.INHERITED,
+        UnicodeScript.COMMON,
+        UnicodeScript.ARABIC,
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.COMMON,
+        UnicodeScript.LATIN,
+        UnicodeScript.COMMON,
+        UnicodeScript.KATAKANA,
+        UnicodeScript.COMMON,
+        UnicodeScript.KATAKANA,
+        UnicodeScript.COMMON,
+        UnicodeScript.HANGUL,
+        UnicodeScript.COMMON,
+        UnicodeScript.LINEAR_B,
+        UnicodeScript.COMMON,
+        UnicodeScript.GREEK,
+        UnicodeScript.COMMON,
+        UnicodeScript.INHERITED,
+        UnicodeScript.LYCIAN,
+        UnicodeScript.CARIAN,
+        UnicodeScript.OLD_ITALIC,
+        UnicodeScript.GOTHIC,
+        UnicodeScript.UGARITIC,
+        UnicodeScript.OLD_PERSIAN,
+        UnicodeScript.DESERET,
+        UnicodeScript.SHAVIAN,
+        UnicodeScript.OSMANYA,
+        UnicodeScript.CYPRIOT,
+        UnicodeScript.IMPERIAL_ARAMAIC,
+        UnicodeScript.PHOENICIAN,
+        UnicodeScript.LYDIAN,
+        UnicodeScript.MEROITIC_HIEROGLYPHS,
+        UnicodeScript.MEROITIC_CURSIVE,
+        UnicodeScript.KHAROSHTHI,
+        UnicodeScript.OLD_SOUTH_ARABIAN,
+        UnicodeScript.AVESTAN,
+        UnicodeScript.INSCRIPTIONAL_PARTHIAN,
+        UnicodeScript.INSCRIPTIONAL_PAHLAVI,
+        UnicodeScript.OLD_TURKIC,
+        UnicodeScript.ARABIC,
+        UnicodeScript.BRAHMI,
+        UnicodeScript.KAITHI,
+        UnicodeScript.SORA_SOMPENG,
+        UnicodeScript.CHAKMA,
+        UnicodeScript.SHARADA,
+        UnicodeScript.TAKRI,
+        UnicodeScript.CUNEIFORM,
+        UnicodeScript.EGYPTIAN_HIEROGLYPHS,
+        UnicodeScript.BAMUM,
+        UnicodeScript.MIAO,
+        UnicodeScript.KATAKANA,
+        UnicodeScript.HIRAGANA,
+        UnicodeScript.COMMON,
+        UnicodeScript.INHERITED,
+        UnicodeScript.COMMON,
+        UnicodeScript.INHERITED,
+        UnicodeScript.COMMON,
+        UnicodeScript.INHERITED,
+        UnicodeScript.COMMON,
+        UnicodeScript.INHERITED,
+        UnicodeScript.COMMON,
+        UnicodeScript.GREEK,
+        UnicodeScript.COMMON,
+        UnicodeScript.ARABIC,
+        UnicodeScript.COMMON,
+        UnicodeScript.HIRAGANA,
+        UnicodeScript.COMMON,
+        UnicodeScript.HAN,
+        UnicodeScript.COMMON,
+        UnicodeScript.INHERITED,
+        UnicodeScript.UNKNOWN
+    )
+}

+ 0 - 64
app/src/main/java/ch/threema/app/utils/UrlUtil.java

@@ -1,64 +0,0 @@
-/*  _____ _
- * |_   _| |_  _ _ ___ ___ _ __  __ _
- *   | | | ' \| '_/ -_) -_) '  \/ _` |_
- *   |_| |_||_|_| \___\___|_|_|_\__,_(_)
- *
- * Threema for Android
- * Copyright (c) 2019-2022 Threema GmbH
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- */
-
-package ch.threema.app.utils;
-
-import android.net.Uri;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.regex.Pattern;
-
-import androidx.annotation.NonNull;
-
-public class UrlUtil {
-	private static final Pattern ascii = Pattern.compile("^[\\x00-\\x7F]*$");
-	private static final Pattern nonAscii = Pattern.compile("^[^\\x00-\\x7F]*$");
-
-	/**
-	 * Checks if the hostname of a given Uri consists of a mix of ascii and non-ascii characters implying a possible phishing attempt through similar looking characters
-	 * @param uri Uri to check
-	 * @return true if hostname consists of either ascii or non-ascii characters only, false in case of a mix of ascii and non-ascii chars or if hostname is empty
-	 */
-	public static boolean isLegalUri(@NonNull Uri uri) {
-		final String host = uri.getHost();
-		if (!TestUtil.empty(host)) {
-			final String strippedHost = host.replaceAll("\\.", "");
-
-			return ascii.matcher(strippedHost).matches() || nonAscii.matcher(strippedHost).matches();
-		}
-		return false;
-	}
-
-	/**
-	 * A version of URLEncoder.encode that cannot fail.
-	 *
-	 * The encoding used is always UTF-8.
-	 */
-	public static String urlencode(@NonNull String value) {
-		try {
-			return URLEncoder.encode(value, "UTF-8");
-		} catch (UnsupportedEncodingException ignored) {
-			// Should never happen since UTF-8 is hardcoded.
-		}
-		return "";
-	}
-}

+ 125 - 0
app/src/main/java/ch/threema/app/utils/UrlUtil.kt

@@ -0,0 +1,125 @@
+/*  _____ _
+ * |_   _| |_  _ _ ___ ___ _ __  __ _
+ *   | | | ' \| '_/ -_) -_) '  \/ _` |_
+ *   |_| |_||_|_| \___\___|_|_|_\__,_(_)
+ *
+ * Threema for Android
+ * Copyright (c) 2022 Threema GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package ch.threema.app.utils
+
+import android.net.Uri
+import java.io.UnsupportedEncodingException
+import java.net.URLEncoder
+import java.util.regex.Pattern
+
+object UrlUtil {
+
+    private val ascii: Pattern = Pattern.compile("^[\\x00-\\x7F]*$")
+
+    /**
+     * Sets of scripts that may be mixed without a warning.
+     *
+     * For example, Hiragana, Katakana, Han and Latin are frequently mixed, this is OK.
+     */
+    private val validUnicodeScriptSets = listOf<Set<UnicodeUtil.UnicodeScript>>(
+        HashSet<UnicodeUtil.UnicodeScript>().apply {
+            add(UnicodeUtil.UnicodeScript.LATIN)
+            add(UnicodeUtil.UnicodeScript.HAN)
+            add(UnicodeUtil.UnicodeScript.HIRAGANA)
+            add(UnicodeUtil.UnicodeScript.KATAKANA)
+        },
+        HashSet<UnicodeUtil.UnicodeScript>().apply {
+            add(UnicodeUtil.UnicodeScript.LATIN)
+            add(UnicodeUtil.UnicodeScript.HAN)
+            add(UnicodeUtil.UnicodeScript.BOPOMOFO)
+        },
+        HashSet<UnicodeUtil.UnicodeScript>().apply {
+            add(UnicodeUtil.UnicodeScript.LATIN)
+            add(UnicodeUtil.UnicodeScript.HAN)
+            add(UnicodeUtil.UnicodeScript.HANGUL)
+        }
+    )
+
+    /**
+     * Checks if the hostname of a given Uri consists of a mix of different unicode scripts implying a possible phishing attempt through similar looking characters.
+     * Additionally, all the characters of the hostname must be valid unicode identifiers per UTS 39.
+     * The script mixing rules apply for each domain label (component) separately.
+     *
+     * This is partially inspired by validation rules in Chromium and Firefox
+     * @see <a href="https://chromium.googlesource.com/chromium/src/+/main/docs/idn.md">Chromium IDN</a>
+     * @see <a href="https://wiki.mozilla.org/IDN_Display_Algorithm#Algorithm">Mozilla IDN Display Algorithm</a>
+     *
+     * @param uri Uri to check
+     * @return true if hostname consists of either ascii or non-ascii characters only, false in case of a mix of ascii and non-ascii chars or if hostname is empty
+     */
+    @JvmStatic
+    fun isLegalUri(uri: Uri): Boolean {
+        val host = uri.host
+
+        if (host.isNullOrEmpty()) {
+            return false
+        }
+
+        val components = host.split(".").filter { it.isNotEmpty() }
+
+        if (components.isEmpty()) {
+            return false
+        }
+
+        return components.map(::isLegalComponent).none { !it }
+    }
+
+    /**
+     * A version of URLEncoder.encode that cannot fail.
+     *
+     * The encoding used is always UTF-8.
+     */
+    @JvmStatic
+    fun urlencode(value: String): String {
+        try {
+            return URLEncoder.encode(value, "UTF-8")
+        } catch (encodingException: UnsupportedEncodingException) {
+            throw AssertionError(encodingException)
+        }
+    }
+
+    /**
+     * Checks that the given component consists of only ASCII or allowed mixable scripts.
+     */
+    private fun isLegalComponent(component: String): Boolean {
+        // Skip further tests if the component consists of ASCII only
+        if (ascii.matcher(component).matches()) {
+            return true
+        }
+
+        // Check that every character belongs to the allowed identifiers per UTS 39
+        if (component.any { !Character.isUnicodeIdentifierPart(it) }) {
+            return false
+        }
+
+        // Check that script mixing is only allowed based on the "Highly Restrictive" profile of UTS 39
+        val scripts = component.map(UnicodeUtil::getScript)
+            .filter { it != UnicodeUtil.UnicodeScript.COMMON && it != UnicodeUtil.UnicodeScript.INHERITED }
+            .toSet()
+
+        if (scripts.size >= 2 && !validUnicodeScriptSets.any { scripts.subtract(it).isEmpty() }) {
+            return false
+        }
+
+        return true
+    }
+}

+ 129 - 122
app/src/main/java/ch/threema/app/voip/PeerConnectionClient.java

@@ -51,12 +51,10 @@ import org.webrtc.MediaConstraints;
 import org.webrtc.MediaStream;
 import org.webrtc.MediaStreamTrack;
 import org.webrtc.PeerConnection;
-import org.webrtc.PeerConnection.IceConnectionState;
 import org.webrtc.PeerConnection.IceGatheringState;
 import org.webrtc.PeerConnectionFactory;
 import org.webrtc.RTCStatsCollectorCallback;
 import org.webrtc.RtpParameters;
-import org.webrtc.RtpReceiver;
 import org.webrtc.RtpSender;
 import org.webrtc.RtpTransceiver;
 import org.webrtc.SdpObserver;
@@ -90,7 +88,6 @@ import java.util.Timer;
 import java.util.TimerTask;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
@@ -114,8 +111,8 @@ import ch.threema.app.voip.util.VoipUtil;
 import ch.threema.app.voip.util.VoipVideoParams;
 import ch.threema.app.webrtc.DataChannelObserver;
 import ch.threema.app.webrtc.UnboundedFlowControlledDataChannel;
-import ch.threema.domain.protocol.api.APIConnector;
 import ch.threema.base.utils.LoggingUtil;
+import ch.threema.domain.protocol.api.APIConnector;
 import ch.threema.protobuf.callsignaling.CallSignaling;
 import java8.util.concurrent.CompletableFuture;
 import java8.util.stream.StreamSupport;
@@ -213,8 +210,8 @@ public class PeerConnectionClient {
 	private SessionDescription localSdp = null; // either offer or answer SDP
 
 	// Workaround for ANDR-1079 / CRBUG 935905
-	private @Nullable Long setRemoteDescriptionNanotime = null;
-	private @Nullable ScheduledFuture<?> iceFailedFuture = null;
+	private @Nullable CompletableFuture<?> transportExpectedStableFuture = null;
+	private @Nullable CompletableFuture<?> transportFailedFuture = null;
 
 	// Workaround for ANDR-1119
 	private @Nullable List<RtpTransceiver> cachedRtpTransceivers = null;
@@ -331,31 +328,27 @@ public class PeerConnectionClient {
 
 		/**
 		 * Callback fired once connection is starting to check candidate pairs
-		 * (IceConnectionState is CHECKING).
+		 * and attempting the DTLS handshake.
 		 */
-		void onIceChecking(long callId);
+		void onTransportConnecting(long callId);
 
 		/**
-		 * Callback fired once connection is established (IceConnectionState is
-		 * CONNECTED).
+		 * Callback fired once connection is established.
 		 */
-		void onIceConnected(long callId);
+		void onTransportConnected(long callId);
 
 		/**
-		 * Callback fired once connection is closed (IceConnectionState is
-		 * DISCONNECTED).
+		 * Callback fired once connection is temporarily disconnected.
+		 *
+		 * Note: This state is recoverable.
 		 */
-		void onIceDisconnected(long callId);
+		void onTransportDisconnected(long callId);
 
 		/**
-		 * Callback fired if connection fails (IceConnectionState is
-		 * FAILED).
-		 *
-		 * NOTE: Due to ANDR-1079 (CRBUG 935905), this will not be called
-		 *       earlier than 15 seconds after the connection attempt was started.
+		 * Callback fired once the connection failed.
 		 */
 		@AnyThread
-		void onIceFailed(long callId);
+		void onTransportFailed(long callId);
 
 		/**
 		 * Callback fired if the ICE gathering state changes.
@@ -852,13 +845,16 @@ public class PeerConnectionClient {
 
 	@WorkerThread
 	private void closeInternal() {
-		// Cancel ICE failed future and reset time variables
-		if (this.iceFailedFuture != null) {
-			this.iceFailedFuture.cancel(true);
-			this.iceFailedFuture = null;
-			logger.info("iceFailedFuture: Cancelled (closeInternal)");
+		// Cancel transport futures
+		if (this.transportFailedFuture != null) {
+			this.transportFailedFuture.cancel(true);
+			this.transportFailedFuture = null;
+			logger.info("transportFailedFuture: Cancelled (closeInternal)");
+		}
+		if (transportExpectedStableFuture != null) {
+			transportExpectedStableFuture.cancel(true);
+			transportExpectedStableFuture = null;
 		}
-		this.setRemoteDescriptionNanotime = null;
 
 		// Stop creating further stats requests
 		logger.debug("Clearing periodic stats timers");
@@ -1405,6 +1401,77 @@ public class PeerConnectionClient {
 	private class PCObserver implements PeerConnection.Observer {
 		@NonNull private Set<String> relatedAddresses = new HashSet<>();
 
+		@Override
+		public void onConnectionChange(PeerConnection.PeerConnectionState newState) {
+			executor.execute(() -> {
+				logger.info("Transport connection state change to {}", newState);
+				if (newState == PeerConnection.PeerConnectionState.CONNECTING) {
+					events.onTransportConnecting(callId);
+				} else if (newState == PeerConnection.PeerConnectionState.CONNECTED) {
+					// Cancel any pending "fail transport after x seconds" future
+					if (transportFailedFuture != null) {
+						// Note: Because the transportFailedFuture is also scheduled on the same executor
+						// as this code, it should not be possible that the scheduled task is
+						// already running.
+						transportFailedFuture.cancel(false);
+						logger.info("transportFailedFuture: Cancelled (because transport reconnected)");
+						transportFailedFuture = null;
+					}
+					events.onTransportConnected(callId);
+				} else if (newState == PeerConnection.PeerConnectionState.DISCONNECTED) {
+					// Schedule to fail the transport after 10s
+					if (transportFailedFuture == null) {
+						logger.info("Scheduling to fail transport in 10s if not reconnected in the meantime");
+						//noinspection Convert2Lambda
+						transportFailedFuture = CompletableFuture.runAsync(new Runnable() {
+							@Override
+							@AnyThread
+							public void run() {
+								logger.info("transportFailedFuture: Time's up, calling onTransportFailed");
+								events.onTransportFailed(callId);
+							}
+						}, CompletableFuture.delayedExecutor(10, TimeUnit.SECONDS, executor));
+					}
+					events.onTransportDisconnected(callId);
+				} else if (newState == PeerConnection.PeerConnectionState.FAILED) {
+					// Cancel any pending "fail transport after x seconds" future
+					if (transportFailedFuture != null) {
+						// Note: Because the transportFailedFuture is also scheduled on the same executor
+						// as this code, it should not be possible that the scheduled task is
+						// already running.
+						transportFailedFuture.cancel(false);
+						logger.info("transportFailedFuture: Cancelled (because transport failed explicitly)");
+						transportFailedFuture = null;
+					}
+
+					// libwebrtc has a bug where intermittent FAILED states may occur. See
+					// ANDR-1079 and CRBUG 935905 for more details.
+					//
+					// As a workaround, we only forward the FAILED state after the transport is
+					// expected to be stable.
+					if (transportExpectedStableFuture == null) {
+						// The FAILED state should not trigger before we have set a remote description
+						logger.error("transportExpectedStableFuture is null as transport connection state moved into FAILED");
+						events.onTransportFailed(callId);
+					} else {
+						// We use the same future to delay the FAILED state so an intermittent
+						// FAILED state will be cancelled by any other state.
+						if (!transportExpectedStableFuture.isDone()) {
+							logger.info("transportFailedFuture: Delaying onTransportFailed call until the transport is expected to be 'stable'");
+						}
+						//noinspection Convert2Lambda
+						transportFailedFuture = transportExpectedStableFuture.thenRun(new Runnable() {
+							@Override
+							@AnyThread
+							public void run() {
+								events.onTransportFailed(callId);
+							}
+						});
+					}
+				}
+			});
+		}
+
 		@Override
 		public void onIceCandidate(final IceCandidate candidate) {
 			logger.info("New local ICE candidate: {}", candidate.sdp);
@@ -1440,11 +1507,7 @@ public class PeerConnectionClient {
 		}
 
 		@Override
-		public void onIceCandidatesRemoved(final IceCandidate[] candidates) {
-			if (logger.isInfoEnabled()) {
-				logger.info("Ignoring removed candidates: {}", Arrays.toString(candidates));
-			}
-		}
+		public void onIceCandidatesRemoved(final IceCandidate[] candidates) {}
 
 		@Override
 		public void onSignalingChange(PeerConnection.SignalingState newState) {
@@ -1453,64 +1516,7 @@ public class PeerConnectionClient {
 
 		@Override
 		public void onIceConnectionChange(final PeerConnection.IceConnectionState newState) {
-			executor.execute(() -> {
-				logger.info("ICE connection state change to {}", newState);
-				if (newState == IceConnectionState.CHECKING) {
-					events.onIceChecking(callId);
-				} else if (newState == IceConnectionState.CONNECTED) {
-					if (iceFailedFuture != null) {
-						// Note: Because the iceFailedFuture is also scheduled on the same executor
-						// as this code, it should not be possible that the scheduled task is
-						// already running.
-						iceFailedFuture.cancel(false);
-						logger.info("iceFailedFuture: Cancelled (connected)");
-						iceFailedFuture = null;
-					}
-					events.onIceConnected(callId);
-				} else if (newState == IceConnectionState.DISCONNECTED) {
-					events.onIceDisconnected(callId);
-				} else if (newState == IceConnectionState.FAILED) {
-					logger.warn("IceConnectionState changed to FAILED");
-					// Note: LibWebRTC has a bug where FAILED is not a terminal state. Sometimes
-					// the IceConnectionState changes to FAILED before all candidates have been
-					// received, and then changes to CONNECTED once the connection has been
-					// established a few ms later. See ANDR-1079 and CRBUG 935905 for more details.
-					// As a workaround, we only fire `onIceFailed` if the state does not switch
-					// to CONNECTED within 15s after setting the remote description.
-					long minimalWaitingTimeSeconds = 15;
-					if (setRemoteDescriptionNanotime == null) {
-						// This should not happen
-						logger.error("createOfferAnswerNanotime is null in onIceConnectionState");
-						events.onIceFailed(callId);
-					} else {
-						// Elapsed nanoseconds since the remote description was set
-						final long elapsedNs = System.nanoTime() - setRemoteDescriptionNanotime;
-						// Max waiting time in nanoseconds
-						final long waitingTimeNs = minimalWaitingTimeSeconds * 1000000000L;
-
-						if (elapsedNs > waitingTimeNs) {
-							// Minimal waiting time already exceeded, trigger event immediately
-							events.onIceFailed(callId);
-						} else {
-							// Less than 15s since remote description was set. Schedule the call to
-							// events.onIceFailed unless it's already scheduled.
-							if (iceFailedFuture == null) {
-								final long remainingNs = waitingTimeNs - elapsedNs;
-								logger.info("iceFailedFuture: Delaying onIceFailed call, {} ms remaining", remainingNs / 1000000);
-								//noinspection Convert2Lambda
-								iceFailedFuture = executor.schedule(new Runnable() {
-									@Override
-									@AnyThread
-									public void run() {
-										logger.info("iceFailedFuture: Time's up, calling onIceFailed");
-										events.onIceFailed(callId);
-									}
-								}, remainingNs, TimeUnit.NANOSECONDS);
-							}
-						}
-					}
-				}
-			});
+			logger.info("ICE connection state change to {}", newState);
 		}
 
 		@Override
@@ -1520,19 +1526,13 @@ public class PeerConnectionClient {
 		}
 
 		@Override
-		public void onIceConnectionReceivingChange(boolean receiving) {
-			logger.info("ICe connection receiving state change to {}", receiving);
-		}
+		public void onIceConnectionReceivingChange(boolean receiving) {}
 
 		@Override
-		public void onAddStream(final MediaStream stream) {
-			logger.warn("Warning: onAddStream (even though we use unified plan)");
-		}
+		public void onAddStream(final MediaStream stream) {}
 
 		@Override
-		public void onRemoveStream(final MediaStream stream) {
-			logger.warn("Warning: onRemoveStream (even though we use unified plan)");
-		}
+		public void onRemoveStream(final MediaStream stream) {}
 
 		@Override
 		public void onDataChannel(final DataChannel dc) {
@@ -1547,16 +1547,6 @@ public class PeerConnectionClient {
 		public void onRenegotiationNeeded() {
 			logger.info("Renegotiation needed");
 		}
-
-		@Override
-		public void onAddTrack(final RtpReceiver receiver, final MediaStream[] mediaStreams) {
-			logger.debug("onAddTrack");
-		}
-
-		@Override
-		public void onTrack(RtpTransceiver transceiver) {
-			logger.debug("onTrack");
-		}
 	}
 
 	// Implementation detail: handle offer creation/signaling and answer setting,
@@ -1609,17 +1599,12 @@ public class PeerConnectionClient {
 					if (peerConnection.getRemoteDescription() == null) {
 						// We've just set our local SDP so time to send it.
 						logger.info("Local SDP set succesfully");
-						if (events != null) {
-							events.onLocalDescription(callId, localSdp);
-						}
+						onLocalDescriptionSet();
 					} else {
 						// We've just set remote description, so drain remote
 						// and send local ICE candidates.
 						logger.info("Remote SDP set succesfully");
-						setRemoteDescriptionNanotime = System.nanoTime();
-						if (events != null) {
-							events.onRemoteDescriptionSet(callId);
-						}
+						onRemoteDescriptionSet();
 						drainCandidates();
 					}
 				} else {
@@ -1629,23 +1614,45 @@ public class PeerConnectionClient {
 						// We've just set our local SDP so time to send it, drain
 						// remote and send local ICE candidates.
 						logger.info("Local SDP set succesfully");
-						if (events != null) {
-							events.onLocalDescription(callId, localSdp);
-						}
+						onLocalDescriptionSet();
 						drainCandidates();
 					} else {
 						// We've just set remote SDP - do nothing for now -
 						// answer will be created soon.
 						logger.info("Remote SDP set succesfully");
-						setRemoteDescriptionNanotime = System.nanoTime();
-						if (events != null) {
-							events.onRemoteDescriptionSet(callId);
-						}
+						onRemoteDescriptionSet();
 					}
 				}
 			});
 		}
 
+		private void onLocalDescriptionSet() {
+			if (events != null) {
+				events.onLocalDescription(callId, localSdp);
+			}
+		}
+
+		private void onRemoteDescriptionSet() {
+			if (events != null) {
+				events.onRemoteDescriptionSet(callId);
+			}
+
+			// Schedule to expect the transport to be 'stable' after 10s. This is a workaround
+			// for intermittent FAILED states.
+			if (transportExpectedStableFuture != null) {
+				logger.error("transportExpectedStableFuture was already running!");
+				transportExpectedStableFuture.cancel(true);
+			}
+			//noinspection Convert2Lambda
+			transportExpectedStableFuture = CompletableFuture.runAsync(new Runnable() {
+				@Override
+				@AnyThread
+				public void run() {
+					logger.info("transportExpectedStableFuture: Transport is expected to be 'stable' now");
+				}
+			}, CompletableFuture.delayedExecutor(10, TimeUnit.SECONDS, executor));
+		}
+
 		@Override
 		public void onCreateFailure(final String error) {
 			reportError("SDP onCreateFailure: " + error, true);

+ 7 - 2
app/src/main/java/ch/threema/app/voip/VoipBluetoothManager.java

@@ -31,6 +31,7 @@
 
 package ch.threema.app.voip;
 
+import android.Manifest;
 import android.annotation.SuppressLint;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
@@ -328,7 +329,7 @@ public class VoipBluetoothManager {
 	public void start() {
 		ThreadUtils.checkIsOnMainThread();
 		logger.debug("start");
-		if (!hasPermission(apprtcContext, android.Manifest.permission.BLUETOOTH)) {
+		if (!hasPermission(apprtcContext, android.Manifest.permission.BLUETOOTH) && !hasPermission(apprtcContext, Manifest.permission.BLUETOOTH_CONNECT)) {
 			logger.warn("Process (pid=" + Process.myPid() + ") lacks BLUETOOTH permission");
 			return;
 		}
@@ -381,7 +382,11 @@ public class VoipBluetoothManager {
 	 */
 	public void stop() {
 		ThreadUtils.checkIsOnMainThread();
-		unregisterReceiver(bluetoothHeadsetReceiver);
+		try {
+			unregisterReceiver(bluetoothHeadsetReceiver);
+		} catch (IllegalArgumentException e) {
+			logger.error("Unable to unregister bluetooth headset receiver", e);
+		}
 		logger.debug("stop: BT state=" + bluetoothState);
 		if (bluetoothAdapter != null) {
 			// Stop BT SCO connection with remote device if needed.

+ 17 - 13
app/src/main/java/ch/threema/app/voip/activities/CallActivity.java

@@ -999,19 +999,21 @@ public class CallActivity extends ThreemaActivity implements
 			}
 		}
 
-		// stop capturing
-		if ((voipStateService.getVideoRenderMode() & VIDEO_RENDER_FLAG_OUTGOING) == VIDEO_RENDER_FLAG_OUTGOING) {
-			// disable outgoing video
-			VoipUtil.sendVoipBroadcast(getApplicationContext(), VoipCallService.ACTION_STOP_CAPTURING);
-
-			// make sure outgoing flag is cleared
-			voipStateService.setVideoRenderMode(voipStateService.getVideoRenderMode() & ~VIDEO_RENDER_FLAG_OUTGOING);
-		}
+		if (this.voipStateService != null) {
+			// stop capturing
+			if ((voipStateService.getVideoRenderMode() & VIDEO_RENDER_FLAG_OUTGOING) == VIDEO_RENDER_FLAG_OUTGOING) {
+				// disable outgoing video
+				VoipUtil.sendVoipBroadcast(getApplicationContext(), VoipCallService.ACTION_STOP_CAPTURING);
+
+				// make sure outgoing flag is cleared
+				voipStateService.setVideoRenderMode(voipStateService.getVideoRenderMode() & ~VIDEO_RENDER_FLAG_OUTGOING);
+			}
 
-		// Unset video target
-		if (this.voipStateService.getVideoContext() != null) {
-			this.voipStateService.getVideoContext().setLocalVideoSinkTarget(null);
-			this.voipStateService.getVideoContext().setRemoteVideoSinkTarget(null);
+			// Unset video target
+			if (this.voipStateService.getVideoContext() != null) {
+				this.voipStateService.getVideoContext().setLocalVideoSinkTarget(null);
+				this.voipStateService.getVideoContext().setRemoteVideoSinkTarget(null);
+			}
 		}
 
 		// Release connection
@@ -1039,7 +1041,9 @@ public class CallActivity extends ThreemaActivity implements
 			this.videoViews = null;
 		}
 
-		this.preferenceService.setPipPosition(pipPosition);
+		if (this.preferenceService != null) {
+			this.preferenceService.setPipPosition(pipPosition);
+		}
 
 		// remove lockscreen keepalive
 		keepAliveHandler.removeCallbacksAndMessages(null);

+ 12 - 12
app/src/main/java/ch/threema/app/voip/activities/WebRTCDebugActivity.java

@@ -322,30 +322,30 @@ public class WebRTCDebugActivity extends ThreemaToolbarActivity implements PeerC
 	}
 
 	@Override
-	public void onIceChecking(long callId) {
-		logger.info("onIceChecking");
-		this.addToLog("ICE Checking");
+	public void onTransportConnecting(long callId) {
+		logger.info("onTransportConnecting");
+		this.addToLog("Transport connecting");
 	}
 
 	@Override
 	@AnyThread
-	public void onIceConnected(long callId) {
-		logger.info("onIceConnected");
-		this.addToLog("ICE Connected");
+	public void onTransportConnected(long callId) {
+		logger.info("onTransportConnected");
+		this.addToLog("Transport connected");
 	}
 
 	@Override
 	@AnyThread
-	public void onIceDisconnected(long callId) {
-		logger.info("onIceDisconnected");
-		this.addToLog("ICE Disconnected");
+	public void onTransportDisconnected(long callId) {
+		logger.info("onTransportDisconnected");
+		this.addToLog("Transport disconnected");
 	}
 
 	@Override
 	@AnyThread
-	public void onIceFailed(long callId) {
-		logger.info("onIceFailed");
-		this.addToLog("ICE Failed");
+	public void onTransportFailed(long callId) {
+		logger.info("onTransportFailed");
+		this.addToLog("Transport failed");
 	}
 
 	@Override

+ 35 - 35
app/src/main/java/ch/threema/app/voip/services/VoipCallService.java

@@ -187,8 +187,8 @@ public class VoipCallService extends LifecycleService implements PeerConnectionC
 	private static boolean isRunning = false;
 
 	private boolean foregroundStarted = false;
-	private boolean iceConnected = false;
-	private boolean iceWasConnected = false;
+	private boolean transportConnected = false;
+	private boolean transportWasConnected = false;
 	private boolean isError = false;
 	private boolean micEnabled = true;
 	private boolean uiDebugStatsEnabled = false;
@@ -233,8 +233,8 @@ public class VoipCallService extends LifecycleService implements PeerConnectionC
 	private static final long FRAME_DETECTOR_QUERY_INTERVAL_MS = 750;
 
 	// Timeouts
-	private final Timer iceDisconnectedSoundTimer = new Timer();
-	private TimerTask iceDisconnectedSoundTimeout;
+	private final Timer transportDisconnectedSoundTimer = new Timer();
+	private TimerTask transportDisconnectedSoundTimeout;
 	private static final int ICE_DISCONNECTED_SOUND_TIMEOUT_MS = 1000;
 
 	// Camera handling
@@ -841,7 +841,7 @@ public class VoipCallService extends LifecycleService implements PeerConnectionC
 		}
 
 		// Initialize state variables
-		this.iceConnected = false;
+		this.transportConnected = false;
 		this.isError = false;
 		this.voipStateService.setStateInitializing(callId);
 
@@ -1362,11 +1362,11 @@ public class VoipCallService extends LifecycleService implements PeerConnectionC
 		logger.info("Cleaning up resources");
 
 		// Stop timers
-		synchronized (this.iceDisconnectedSoundTimer) {
+		synchronized (this.transportDisconnectedSoundTimer) {
 			logger.info("Cancel iceDisconnectedSoundTimeout");
-			if (this.iceDisconnectedSoundTimeout != null) {
-				this.iceDisconnectedSoundTimeout.cancel();
-				this.iceDisconnectedSoundTimeout = null;
+			if (this.transportDisconnectedSoundTimeout != null) {
+				this.transportDisconnectedSoundTimeout.cancel();
+				this.transportDisconnectedSoundTimeout = null;
 			}
 		}
 
@@ -1380,7 +1380,7 @@ public class VoipCallService extends LifecycleService implements PeerConnectionC
 		if (this.peerConnectionClient != null) {
 			// Note: This needs to be here to ensure that the stats-after-closing contains all
 			//       candidate pairs.
-			this.iceConnected = false;
+			this.transportConnected = false;
 
 			synchronized (this) {
 				logger.info("Unregister debug stats collector");
@@ -1455,7 +1455,7 @@ public class VoipCallService extends LifecycleService implements PeerConnectionC
 		logCallInfo(
 			callId,
 			"disconnect (isConnected? {} | isError? {} | message: {})",
-			this.iceConnected, this.isError, message
+			this.transportConnected, this.isError, message
 		);
 
 		// If the call is still connected, notify listeners about the finishing
@@ -1483,7 +1483,7 @@ public class VoipCallService extends LifecycleService implements PeerConnectionC
 
 		stopForeground(true);
 
-		if (this.iceConnected && !this.isError) {
+		if (this.transportConnected && !this.isError) {
 			VoipUtil.sendVoipBroadcast(this, CallActivity.ACTION_DISCONNECTED);
 		} else {
 			VoipUtil.sendVoipBroadcast(this, CallActivity.ACTION_CANCELLED);
@@ -1799,8 +1799,8 @@ public class VoipCallService extends LifecycleService implements PeerConnectionC
 
 	@Override
 	@AnyThread
-	public void onIceChecking(long callId) {
-		logCallInfo(callId, "ICE checking");
+	public void onTransportConnecting(long callId) {
+		logCallInfo(callId, "Transport connecting");
 		synchronized (this) {
 			if (this.peerConnectionClient != null) {
 				// Register debug stats collector (fast interval until connected)
@@ -1824,16 +1824,16 @@ public class VoipCallService extends LifecycleService implements PeerConnectionC
 
 	@Override
 	@AnyThread
-	public void onIceConnected(long callId) {
-		logCallInfo(callId, "ICE connected (wasConnected={})", this.iceWasConnected);
-		this.iceConnected = true;
-		if (this.iceWasConnected) {
+	public void onTransportConnected(long callId) {
+		logCallInfo(callId, "Transport connected (wasConnected={})", this.transportWasConnected);
+		this.transportConnected = true;
+		if (this.transportWasConnected) {
 			// If we were previously connected, then the connection problem sound
 			// is scheduled or playing right now. Cancel and stop it.
-			synchronized (this.iceDisconnectedSoundTimer) {
-				if (this.iceDisconnectedSoundTimeout != null) {
-					this.iceDisconnectedSoundTimeout.cancel();
-					this.iceDisconnectedSoundTimeout = null;
+			synchronized (this.transportDisconnectedSoundTimer) {
+				if (this.transportDisconnectedSoundTimeout != null) {
+					this.transportDisconnectedSoundTimeout.cancel();
+					this.transportDisconnectedSoundTimeout = null;
 				}
 			}
 			boolean wasPlaying = this.mediaPlayer != null;
@@ -1851,7 +1851,7 @@ public class VoipCallService extends LifecycleService implements PeerConnectionC
 			}
 		} else {
 			// This is the initial "connected" event
-			this.iceWasConnected = true;
+			this.transportWasConnected = true;
 			this.callConnected(callId);
 			synchronized (this) {
 				// Register debug stats collector (slow interval since we're connected)
@@ -1881,34 +1881,34 @@ public class VoipCallService extends LifecycleService implements PeerConnectionC
 
 	@Override
 	@AnyThread
-	public void onIceDisconnected(final long callId) {
-		// ICE was disconnected. This can be a real closing of the connection,
+	public void onTransportDisconnected(final long callId) {
+		// Transport was disconnected. This can be a real closing of the connection,
 		// or just a temporary connectivity issue that can be recovered.
-		logCallInfo(callId, "ICE disconnected");
-		this.iceConnected = false;
+		logCallInfo(callId, "Transport disconnected");
+		this.transportConnected = false;
 
 		// Notify activity about connectivity problems
 		VoipUtil.sendVoipBroadcast(getApplicationContext(), CallActivity.ACTION_RECONNECTING);
 
 		// Start problem sound with some delay
-		synchronized (this.iceDisconnectedSoundTimer) {
-			this.iceDisconnectedSoundTimeout = new TimerTask() {
+		synchronized (this.transportDisconnectedSoundTimer) {
+			this.transportDisconnectedSoundTimeout = new TimerTask() {
 				@Override
 				public void run() {
 					VoipCallService.this.startLoopingSound(callId, R.raw.threema_problem, "problem");
-					VoipCallService.this.iceDisconnectedSoundTimeout = null;
+					VoipCallService.this.transportDisconnectedSoundTimeout = null;
 				}
 			};
-			this.iceDisconnectedSoundTimer.schedule(this.iceDisconnectedSoundTimeout, ICE_DISCONNECTED_SOUND_TIMEOUT_MS);
+			this.transportDisconnectedSoundTimer.schedule(this.transportDisconnectedSoundTimeout, ICE_DISCONNECTED_SOUND_TIMEOUT_MS);
 		}
 	}
 
 	@Override
-	public void onIceFailed(long callId) {
-		logCallWarning(callId, "ICE failed");
-		this.iceConnected = false;
+	public void onTransportFailed(long callId) {
+		logCallWarning(callId, "Transport failed");
+		this.transportConnected = false;
 
-		if (this.iceWasConnected) {
+		if (this.transportWasConnected) {
 			// If we were previously connected, this means that the connection was closed.
 			RuntimeUtil.runOnUiThread(() -> VoipCallService.this.disconnect(getString(R.string.voip_connection_lost)));
 		} else {

+ 4 - 4
app/src/main/java/ch/threema/app/voip/services/VoipStateService.java

@@ -892,7 +892,7 @@ public class VoipStateService implements AudioManager.OnAudioFocusChangeListener
 			return true;
 		}
 
-		logger.info("Call hangup message received from {}", msg.getFromIdentity());
+		logCallInfo(callId, "Call hangup message received from {}", msg.getFromIdentity());
 
 		final String identity = msg.getFromIdentity();
 
@@ -1044,7 +1044,6 @@ public class VoipStateService implements AudioManager.OnAudioFocusChangeListener
 		final long callId,
 		byte reason
 	) throws ThreemaException, IllegalArgumentException {
-		logger.info("VoipStateService sendRejectCallAnswerMessage");
 		this.sendRejectCallAnswerMessage(receiver, callId, reason, true);
 	}
 
@@ -1058,11 +1057,12 @@ public class VoipStateService implements AudioManager.OnAudioFocusChangeListener
 		byte reason,
 		boolean notifyListeners
 	) throws ThreemaException, IllegalArgumentException {
-		logger.info("VoipStateService sendRejectCallAnswerMessage listener true");
+		logCallInfo(callId, "Sending reject call answer message (reason={})", reason);
 		this.sendCallAnswerMessage(receiver, callId, null, VoipCallAnswerData.Action.REJECT, reason, null);
 
 		// Notify listeners
 		if (notifyListeners) {
+			logCallInfo(callId, "Notifying listeners about call rejection");
 			VoipListenerManager.callEventListener.handle(listener -> {
 				switch (reason) {
 					case VoipCallAnswerData.RejectReason.BUSY:
@@ -1095,7 +1095,7 @@ public class VoipStateService implements AudioManager.OnAudioFocusChangeListener
 	    @Nullable Byte rejectReason,
 		@Nullable Boolean videoCall
 	) throws ThreemaException, IllegalArgumentException, IllegalStateException {
-		logger.info("VoipStateService sendCallAnswerMessage");
+		logCallInfo(callId, "Sending call answer message");
 		final VoipCallAnswerData callAnswerData = new VoipCallAnswerData()
 			.setCallId(callId)
 			.setAction(action);

+ 5 - 5
app/src/main/java/ch/threema/app/webclient/services/instance/message/receiver/AcknowledgeRequestHandler.java

@@ -31,6 +31,7 @@ import androidx.annotation.AnyThread;
 import androidx.annotation.WorkerThread;
 import ch.threema.app.services.MessageService;
 import ch.threema.app.services.NotificationService;
+import ch.threema.app.utils.ConversationNotificationUtil;
 import ch.threema.app.webclient.Protocol;
 import ch.threema.app.webclient.exceptions.ConversionException;
 import ch.threema.app.webclient.services.instance.MessageReceiver;
@@ -99,14 +100,13 @@ public class AcknowledgeRequestHandler extends MessageReceiver {
 			return;
 		}
 
-		// Mark as read first
-		messageService.markMessageAsRead(messageModel, notificationService);
-
 		if (isAcknowledged) {
-			this.messageService.sendUserAcknowledgement(messageModel);
+			this.messageService.sendUserAcknowledgement(messageModel, true);
 		} else {
-			this.messageService.sendUserDecline(messageModel);
+			this.messageService.sendUserDecline(messageModel, true);
 		}
+
+		notificationService.cancelConversationNotification(ConversationNotificationUtil.getUid(messageModel));
 	}
 
 	@Override

+ 39 - 16
app/src/main/res/layout/activity_directory.xml

@@ -65,29 +65,52 @@
 		app:layout_behavior="@string/appbar_scrolling_view_behavior"
 		android:orientation="vertical">
 
-		<com.google.android.material.chip.ChipGroup
-			android:id="@+id/chip_group"
+		<HorizontalScrollView
 			android:layout_width="match_parent"
 			android:layout_height="wrap_content"
-			android:layout_marginLeft="16dp"
-			android:layout_marginRight="16dp"
-			app:chipSpacingHorizontal="4dp"
-			app:chipSpacingVertical="0dp"
-			app:lineSpacing="-4dp"
-			app:layout_constraintBottom_toBottomOf="parent"
-			app:layout_constraintLeft_toLeftOf="parent"
-			app:layout_constraintTop_toTopOf="parent"
-			app:singleLine="false"
-			android:visibility="gone" />
+			android:scrollbarSize="0dp"
+			android:scrollbars="none">
 
-		<ch.threema.app.ui.EmptyRecyclerView
-			android:id="@+id/recycler"
+			<com.google.android.material.chip.ChipGroup
+				android:id="@+id/chip_group"
+				android:layout_width="wrap_content"
+				android:layout_height="wrap_content"
+				android:paddingLeft="16dp"
+				android:paddingTop="0dp"
+				android:paddingRight="16dp"
+				android:paddingBottom="-1dp"
+				app:chipSpacingHorizontal="4dp"
+				app:chipSpacingVertical="0dp"
+				app:singleLine="true"
+				android:visibility="gone" />
+
+		</HorizontalScrollView>
+
+		<FrameLayout
 			android:layout_width="match_parent"
 			android:layout_height="match_parent"
-			android:layout_marginTop="4dp"
 			android:layout_marginLeft="@dimen/tablet_additional_padding_left_right"
 			android:layout_marginRight="@dimen/tablet_additional_padding_left_right"
-			app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+			app:layout_behavior="@string/appbar_scrolling_view_behavior">
+
+		<ch.threema.app.ui.EmptyRecyclerView
+			android:id="@+id/recycler"
+			android:layout_width="match_parent"
+			android:layout_height="match_parent"
+			android:layout_marginTop="4dp"/>
+
+		<TextView
+			android:id="@+id/empty_text"
+			android:layout_width="wrap_content"
+			android:layout_height="wrap_content"
+			android:gravity="center_horizontal|center_vertical"
+			android:layout_marginTop="16dp"
+			android:paddingLeft="16dp"
+			android:paddingRight="16dp"
+			android:textAppearance="@style/Threema.TextAppearance.Emptyview"
+			android:text="@string/directory_empty_view_text" />
+
+		</FrameLayout>
 
 	</LinearLayout>
 

+ 29 - 1
app/src/main/res/layout/dialog_message_detail.xml

@@ -118,13 +118,41 @@
 		android:visibility="gone"
 		/>
 
+	<TextView
+		android:id="@+id/modified_text"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:text="@string/state_dialog_modified"
+		android:layout_marginTop="4dp"
+		app:layout_constraintTop_toBottomOf="@id/read_text"
+		app:layout_constraintLeft_toLeftOf="parent"
+		app:layout_constraintRight_toLeftOf="@+id/modified_date"
+		app:layout_constrainedWidth="true"
+		app:layout_constraintHorizontal_bias="0"
+		android:ellipsize="end"
+		android:singleLine="true"
+		android:textAppearance="@style/Threema.AlertDialog.BodyTextStyle"
+		android:visibility="gone"
+		/>
+
+	<TextView
+		android:id="@+id/modified_date"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:text="12.4.2019 12:49"
+		app:layout_constraintBaseline_toBaselineOf="@id/modified_text"
+		app:layout_constraintRight_toRightOf="parent"
+		android:textAppearance="@style/Threema.AlertDialog.BodyTextStyle"
+		android:visibility="gone"
+		/>
+
 	<TextView
 		android:id="@+id/messageid_text"
 		android:layout_width="wrap_content"
 		android:layout_height="wrap_content"
 		android:text="@string/message_id"
 		android:layout_marginTop="4dp"
-		app:layout_constraintTop_toBottomOf="@id/read_text"
+		app:layout_constraintTop_toBottomOf="@id/modified_text"
 		app:layout_constraintLeft_toLeftOf="parent"
 		app:layout_constraintRight_toLeftOf="@+id/messageid_date"
 		app:layout_constrainedWidth="true"

+ 9 - 0
app/src/main/res/layout/fragment_compose_message.xml

@@ -58,6 +58,15 @@
 				android:elevation="4dp"
 				android:visibility="gone" />
 
+			<ch.threema.app.ui.ReportSpamView
+				android:id="@+id/report_spam_layout"
+				android:layout_width="match_parent"
+				android:layout_height="wrap_content"
+				android:animateLayoutChanges="true"
+				android:background="?attr/background_openNotice"
+				android:elevation="4dp"
+				android:visibility="gone" />
+
 			<RelativeLayout
 				android:layout_width="match_parent"
 				android:layout_height="0dp"

+ 50 - 49
app/src/main/res/layout/header_contact_detail.xml

@@ -7,39 +7,39 @@
 	android:orientation="vertical">
 
 	<LinearLayout
-			android:orientation="vertical"
-			android:layout_width="match_parent"
-			android:layout_height="match_parent"
-			android:paddingRight="16dp"
-			android:paddingLeft="16dp">
+		android:orientation="vertical"
+		android:layout_width="match_parent"
+		android:layout_height="match_parent"
+		android:paddingRight="16dp"
+		android:paddingLeft="16dp">
 
 		<ch.threema.app.ui.SectionHeaderView
-				android:layout_width="match_parent"
-				android:layout_height="wrap_content"
-				android:layout_marginTop="10dp"
-				android:text="@string/title_threemaid"/>
+			android:layout_width="match_parent"
+			android:layout_height="wrap_content"
+			android:layout_marginTop="10dp"
+			android:text="@string/title_threemaid"/>
 
 		<LinearLayout
-				android:id="@+id/threemaid_layout"
-				android:layout_width="match_parent"
-				android:layout_height="wrap_content"
-				android:orientation="horizontal"
-				android:layout_marginTop="10dp"
-				android:clickable="true">
+			android:id="@+id/threemaid_layout"
+			android:layout_width="match_parent"
+			android:layout_height="wrap_content"
+			android:orientation="horizontal"
+			android:layout_marginTop="10dp"
+			android:clickable="true">
 
 			<TextView
-					android:id="@+id/threema_id"
-					android:layout_width="wrap_content"
-					android:layout_height="wrap_content"
-					android:layout_marginRight="16dp"
-					android:textAppearance="@style/Threema.TextAppearance.List.FirstLine"
-					android:singleLine="true"/>
+				android:id="@+id/threema_id"
+				android:layout_width="wrap_content"
+				android:layout_height="wrap_content"
+				android:layout_marginRight="16dp"
+				android:textAppearance="@style/Threema.TextAppearance.List.FirstLine"
+				android:singleLine="true"/>
 
 			<ch.threema.app.ui.VerificationLevelImageView
-					android:id="@+id/verification_level_image"
-					android:layout_width="wrap_content"
-					android:layout_height="wrap_content"
-					android:layout_gravity="center_vertical" />
+				android:id="@+id/verification_level_image"
+				android:layout_width="wrap_content"
+				android:layout_height="wrap_content"
+				android:layout_gravity="center_vertical" />
 
 			<ImageButton
 				android:id="@+id/verification_information_icon"
@@ -51,27 +51,28 @@
 				app:srcCompat="@drawable/ic_info_outline"
 				app:tint="?attr/textColorSecondary" />
 
+
 		</LinearLayout>
 
 		<LinearLayout
-				android:id="@+id/nickname_container"
-				android:orientation="vertical"
-				android:layout_width="match_parent"
-				android:layout_height="match_parent">
+			android:id="@+id/nickname_container"
+			android:orientation="vertical"
+			android:layout_width="match_parent"
+			android:layout_height="match_parent">
 
 			<ch.threema.app.ui.SectionHeaderView
-					android:layout_width="match_parent"
-					android:layout_height="wrap_content"
-					android:paddingTop="10dp"
-					android:text="@string/title_public_nickname"/>
+				android:layout_width="match_parent"
+				android:layout_height="wrap_content"
+				android:paddingTop="10dp"
+				android:text="@string/title_public_nickname"/>
 
 			<ch.threema.app.emojis.EmojiTextView
-					android:id="@+id/public_nickname"
-					android:layout_width="match_parent"
-					android:layout_height="wrap_content"
-					android:layout_marginTop="10dp"
-					android:textAppearance="@style/Threema.TextAppearance.List.FirstLine"
-					android:singleLine="true"/>
+				android:id="@+id/public_nickname"
+				android:layout_width="match_parent"
+				android:layout_height="wrap_content"
+				android:layout_marginTop="10dp"
+				android:textAppearance="@style/Threema.TextAppearance.List.FirstLine"
+				android:singleLine="true"/>
 		</LinearLayout>
 
 		<LinearLayout
@@ -168,18 +169,18 @@
 			android:text="@string/show_public_key" />
 
 		<LinearLayout
-				android:id="@+id/group_members_title_container"
-				android:layout_width="match_parent"
-				android:layout_height="wrap_content"
-				android:layout_marginTop="12dp"
-				android:layout_marginBottom="5dp"
-				android:orientation="vertical"
-				android:visibility="visible">
+			android:id="@+id/group_members_title_container"
+			android:layout_width="match_parent"
+			android:layout_height="wrap_content"
+			android:layout_marginTop="12dp"
+			android:layout_marginBottom="5dp"
+			android:orientation="vertical"
+			android:visibility="visible">
 
 			<ch.threema.app.ui.SectionHeaderView
-					android:layout_width="match_parent"
-					android:layout_height="wrap_content"
-					android:text="@string/group_membership_title"/>
+				android:layout_width="match_parent"
+				android:layout_height="wrap_content"
+				android:text="@string/group_membership_title"/>
 
 		</LinearLayout>
 	</LinearLayout>

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio